1/* 2 comedi/drivers/s526.c 3 Sensoray s526 Comedi driver 4 5 COMEDI - Linux Control and Measurement Device Interface 6 Copyright (C) 2000 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: s526 20Description: Sensoray 526 driver 21Devices: [Sensoray] 526 (s526) 22Author: Richie 23 Everett Wang <everett.wang@everteq.com> 24Updated: Thu, 14 Sep. 2006 25Status: experimental 26 27Encoder works 28Analog input works 29Analog output works 30PWM output works 31Commands are not supported yet. 32 33Configuration Options: 34 35comedi_config /dev/comedi0 s526 0x2C0,0x3 36 37*/ 38 39#include <linux/module.h> 40#include "../comedidev.h" 41#include <asm/byteorder.h> 42 43#define S526_START_AI_CONV 0 44#define S526_AI_READ 0 45 46/* Ports */ 47#define S526_NUM_PORTS 27 48 49/* registers */ 50#define REG_TCR 0x00 51#define REG_WDC 0x02 52#define REG_DAC 0x04 53#define REG_ADC 0x06 54#define REG_ADD 0x08 55#define REG_DIO 0x0A 56#define REG_IER 0x0C 57#define REG_ISR 0x0E 58#define REG_MSC 0x10 59#define REG_C0L 0x12 60#define REG_C0H 0x14 61#define REG_C0M 0x16 62#define REG_C0C 0x18 63#define REG_C1L 0x1A 64#define REG_C1H 0x1C 65#define REG_C1M 0x1E 66#define REG_C1C 0x20 67#define REG_C2L 0x22 68#define REG_C2H 0x24 69#define REG_C2M 0x26 70#define REG_C2C 0x28 71#define REG_C3L 0x2A 72#define REG_C3H 0x2C 73#define REG_C3M 0x2E 74#define REG_C3C 0x30 75#define REG_EED 0x32 76#define REG_EEC 0x34 77 78struct counter_mode_register_t { 79#if defined(__LITTLE_ENDIAN_BITFIELD) 80 unsigned short coutSource:1; 81 unsigned short coutPolarity:1; 82 unsigned short autoLoadResetRcap:3; 83 unsigned short hwCtEnableSource:2; 84 unsigned short ctEnableCtrl:2; 85 unsigned short clockSource:2; 86 unsigned short countDir:1; 87 unsigned short countDirCtrl:1; 88 unsigned short outputRegLatchCtrl:1; 89 unsigned short preloadRegSel:1; 90 unsigned short reserved:1; 91 #elif defined(__BIG_ENDIAN_BITFIELD) 92 unsigned short reserved:1; 93 unsigned short preloadRegSel:1; 94 unsigned short outputRegLatchCtrl:1; 95 unsigned short countDirCtrl:1; 96 unsigned short countDir:1; 97 unsigned short clockSource:2; 98 unsigned short ctEnableCtrl:2; 99 unsigned short hwCtEnableSource:2; 100 unsigned short autoLoadResetRcap:3; 101 unsigned short coutPolarity:1; 102 unsigned short coutSource:1; 103#else 104#error Unknown bit field order 105#endif 106}; 107 108union cmReg { 109 struct counter_mode_register_t reg; 110 unsigned short value; 111}; 112 113struct s526_private { 114 unsigned int gpct_config[4]; 115 unsigned short ai_config; 116}; 117 118static int s526_gpct_rinsn(struct comedi_device *dev, 119 struct comedi_subdevice *s, 120 struct comedi_insn *insn, 121 unsigned int *data) 122{ 123 unsigned int chan = CR_CHAN(insn->chanspec); 124 unsigned long chan_iobase = dev->iobase + chan * 8; 125 unsigned int lo; 126 unsigned int hi; 127 int i; 128 129 for (i = 0; i < insn->n; i++) { 130 /* Read the low word first */ 131 lo = inw(chan_iobase + REG_C0L) & 0xffff; 132 hi = inw(chan_iobase + REG_C0H) & 0xff; 133 134 data[i] = (hi << 16) | lo; 135 } 136 137 return insn->n; 138} 139 140static int s526_gpct_insn_config(struct comedi_device *dev, 141 struct comedi_subdevice *s, 142 struct comedi_insn *insn, 143 unsigned int *data) 144{ 145 struct s526_private *devpriv = dev->private; 146 unsigned int chan = CR_CHAN(insn->chanspec); 147 unsigned long chan_iobase = dev->iobase + chan * 8; 148 unsigned int val; 149 union cmReg cmReg; 150 151 /* Check what type of Counter the user requested, data[0] contains */ 152 /* the Application type */ 153 switch (data[0]) { 154 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: 155 /* 156 data[0]: Application Type 157 data[1]: Counter Mode Register Value 158 data[2]: Pre-load Register Value 159 data[3]: Conter Control Register 160 */ 161 devpriv->gpct_config[chan] = data[0]; 162 163#if 0 164 /* Example of Counter Application */ 165 /* One-shot (software trigger) */ 166 cmReg.reg.coutSource = 0; /* out RCAP */ 167 cmReg.reg.coutPolarity = 1; /* Polarity inverted */ 168 cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */ 169 cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ 170 cmReg.reg.ctEnableCtrl = 2; /* Hardware */ 171 cmReg.reg.clockSource = 2; /* Internal */ 172 cmReg.reg.countDir = 1; /* Down */ 173 cmReg.reg.countDirCtrl = 1; /* Software */ 174 cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ 175 cmReg.reg.preloadRegSel = 0; /* PR0 */ 176 cmReg.reg.reserved = 0; 177 178 outw(cmReg.value, chan_iobase + REG_C0M); 179 180 outw(0x0001, chan_iobase + REG_C0H); 181 outw(0x3C68, chan_iobase + REG_C0L); 182 183 /* Reset the counter */ 184 outw(0x8000, chan_iobase + REG_C0C); 185 /* Load the counter from PR0 */ 186 outw(0x4000, chan_iobase + REG_C0C); 187 188 /* Reset RCAP (fires one-shot) */ 189 outw(0x0008, chan_iobase + REG_C0C); 190 191#endif 192 193#if 1 194 /* Set Counter Mode Register */ 195 cmReg.value = data[1] & 0xffff; 196 outw(cmReg.value, chan_iobase + REG_C0M); 197 198 /* Reset the counter if it is software preload */ 199 if (cmReg.reg.autoLoadResetRcap == 0) { 200 /* Reset the counter */ 201 outw(0x8000, chan_iobase + REG_C0C); 202 /* Load the counter from PR0 203 * outw(0x4000, chan_iobase + REG_C0C); 204 */ 205 } 206#else 207 /* 0 quadrature, 1 software control */ 208 cmReg.reg.countDirCtrl = 0; 209 210 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ 211 if (data[1] == GPCT_X2) 212 cmReg.reg.clockSource = 1; 213 else if (data[1] == GPCT_X4) 214 cmReg.reg.clockSource = 2; 215 else 216 cmReg.reg.clockSource = 0; 217 218 /* When to take into account the indexpulse: */ 219 /*if (data[2] == GPCT_IndexPhaseLowLow) { 220 } else if (data[2] == GPCT_IndexPhaseLowHigh) { 221 } else if (data[2] == GPCT_IndexPhaseHighLow) { 222 } else if (data[2] == GPCT_IndexPhaseHighHigh) { 223 }*/ 224 /* Take into account the index pulse? */ 225 if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) 226 /* Auto load with INDEX^ */ 227 cmReg.reg.autoLoadResetRcap = 4; 228 229 /* Set Counter Mode Register */ 230 cmReg.value = data[1] & 0xffff; 231 outw(cmReg.value, chan_iobase + REG_C0M); 232 233 /* Load the pre-load register high word */ 234 val = (data[2] >> 16) & 0xffff; 235 outw(val, chan_iobase + REG_C0H); 236 237 /* Load the pre-load register low word */ 238 val = data[2] & 0xffff; 239 outw(val, chan_iobase + REG_C0L); 240 241 /* Write the Counter Control Register */ 242 if (data[3]) { 243 val = data[3] & 0xffff; 244 outw(val, chan_iobase + REG_C0C); 245 } 246 /* Reset the counter if it is software preload */ 247 if (cmReg.reg.autoLoadResetRcap == 0) { 248 /* Reset the counter */ 249 outw(0x8000, chan_iobase + REG_C0C); 250 /* Load the counter from PR0 */ 251 outw(0x4000, chan_iobase + REG_C0C); 252 } 253#endif 254 break; 255 256 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: 257 /* 258 data[0]: Application Type 259 data[1]: Counter Mode Register Value 260 data[2]: Pre-load Register 0 Value 261 data[3]: Pre-load Register 1 Value 262 data[4]: Conter Control Register 263 */ 264 devpriv->gpct_config[chan] = data[0]; 265 266 /* Set Counter Mode Register */ 267 cmReg.value = data[1] & 0xffff; 268 cmReg.reg.preloadRegSel = 0; /* PR0 */ 269 outw(cmReg.value, chan_iobase + REG_C0M); 270 271 /* Load the pre-load register 0 high word */ 272 val = (data[2] >> 16) & 0xffff; 273 outw(val, chan_iobase + REG_C0H); 274 275 /* Load the pre-load register 0 low word */ 276 val = data[2] & 0xffff; 277 outw(val, chan_iobase + REG_C0L); 278 279 /* Set Counter Mode Register */ 280 cmReg.value = data[1] & 0xffff; 281 cmReg.reg.preloadRegSel = 1; /* PR1 */ 282 outw(cmReg.value, chan_iobase + REG_C0M); 283 284 /* Load the pre-load register 1 high word */ 285 val = (data[3] >> 16) & 0xffff; 286 outw(val, chan_iobase + REG_C0H); 287 288 /* Load the pre-load register 1 low word */ 289 val = data[3] & 0xffff; 290 outw(val, chan_iobase + REG_C0L); 291 292 /* Write the Counter Control Register */ 293 if (data[4]) { 294 val = data[4] & 0xffff; 295 outw(val, chan_iobase + REG_C0C); 296 } 297 break; 298 299 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: 300 /* 301 data[0]: Application Type 302 data[1]: Counter Mode Register Value 303 data[2]: Pre-load Register 0 Value 304 data[3]: Pre-load Register 1 Value 305 data[4]: Conter Control Register 306 */ 307 devpriv->gpct_config[chan] = data[0]; 308 309 /* Set Counter Mode Register */ 310 cmReg.value = data[1] & 0xffff; 311 cmReg.reg.preloadRegSel = 0; /* PR0 */ 312 outw(cmReg.value, chan_iobase + REG_C0M); 313 314 /* Load the pre-load register 0 high word */ 315 val = (data[2] >> 16) & 0xffff; 316 outw(val, chan_iobase + REG_C0H); 317 318 /* Load the pre-load register 0 low word */ 319 val = data[2] & 0xffff; 320 outw(val, chan_iobase + REG_C0L); 321 322 /* Set Counter Mode Register */ 323 cmReg.value = data[1] & 0xffff; 324 cmReg.reg.preloadRegSel = 1; /* PR1 */ 325 outw(cmReg.value, chan_iobase + REG_C0M); 326 327 /* Load the pre-load register 1 high word */ 328 val = (data[3] >> 16) & 0xffff; 329 outw(val, chan_iobase + REG_C0H); 330 331 /* Load the pre-load register 1 low word */ 332 val = data[3] & 0xffff; 333 outw(val, chan_iobase + REG_C0L); 334 335 /* Write the Counter Control Register */ 336 if (data[4]) { 337 val = data[4] & 0xffff; 338 outw(val, chan_iobase + REG_C0C); 339 } 340 break; 341 342 default: 343 return -EINVAL; 344 } 345 346 return insn->n; 347} 348 349static int s526_gpct_winsn(struct comedi_device *dev, 350 struct comedi_subdevice *s, 351 struct comedi_insn *insn, 352 unsigned int *data) 353{ 354 struct s526_private *devpriv = dev->private; 355 unsigned int chan = CR_CHAN(insn->chanspec); 356 unsigned long chan_iobase = dev->iobase + chan * 8; 357 358 inw(chan_iobase + REG_C0M); /* Is this read required? */ 359 360 /* Check what Application of Counter this channel is configured for */ 361 switch (devpriv->gpct_config[chan]) { 362 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: 363 /* data[0] contains the PULSE_WIDTH 364 data[1] contains the PULSE_PERIOD 365 @pre PULSE_PERIOD > PULSE_WIDTH > 0 366 The above periods must be expressed as a multiple of the 367 pulse frequency on the selected source 368 */ 369 if ((data[1] <= data[0]) || !data[0]) 370 return -EINVAL; 371 372 /* Fall thru to write the PULSE_WIDTH */ 373 374 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: 375 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: 376 outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H); 377 outw(data[0] & 0xffff, chan_iobase + REG_C0L); 378 break; 379 380 default: 381 return -EINVAL; 382 } 383 384 return insn->n; 385} 386 387#define ISR_ADC_DONE 0x4 388static int s526_ai_insn_config(struct comedi_device *dev, 389 struct comedi_subdevice *s, 390 struct comedi_insn *insn, unsigned int *data) 391{ 392 struct s526_private *devpriv = dev->private; 393 int result = -EINVAL; 394 395 if (insn->n < 1) 396 return result; 397 398 result = insn->n; 399 400 /* data[0] : channels was set in relevant bits. 401 data[1] : delay 402 */ 403 /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to 404 * enable channels here. The channel should be enabled in the 405 * INSN_READ handler. */ 406 407 /* Enable ADC interrupt */ 408 outw(ISR_ADC_DONE, dev->iobase + REG_IER); 409 devpriv->ai_config = (data[0] & 0x3ff) << 5; 410 if (data[1] > 0) 411 devpriv->ai_config |= 0x8000; /* set the delay */ 412 413 devpriv->ai_config |= 0x0001; /* ADC start bit */ 414 415 return result; 416} 417 418static int s526_ai_eoc(struct comedi_device *dev, 419 struct comedi_subdevice *s, 420 struct comedi_insn *insn, 421 unsigned long context) 422{ 423 unsigned int status; 424 425 status = inw(dev->iobase + REG_ISR); 426 if (status & ISR_ADC_DONE) 427 return 0; 428 return -EBUSY; 429} 430 431static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, 432 struct comedi_insn *insn, unsigned int *data) 433{ 434 struct s526_private *devpriv = dev->private; 435 unsigned int chan = CR_CHAN(insn->chanspec); 436 int n; 437 unsigned short value; 438 unsigned int d; 439 int ret; 440 441 /* Set configured delay, enable channel for this channel only, 442 * select "ADC read" channel, set "ADC start" bit. */ 443 value = (devpriv->ai_config & 0x8000) | 444 ((1 << 5) << chan) | (chan << 1) | 0x0001; 445 446 /* convert n samples */ 447 for (n = 0; n < insn->n; n++) { 448 /* trigger conversion */ 449 outw(value, dev->iobase + REG_ADC); 450 451 /* wait for conversion to end */ 452 ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0); 453 if (ret) 454 return ret; 455 456 outw(ISR_ADC_DONE, dev->iobase + REG_ISR); 457 458 /* read data */ 459 d = inw(dev->iobase + REG_ADD); 460 461 /* munge data */ 462 data[n] = d ^ 0x8000; 463 } 464 465 /* return the number of samples read/written */ 466 return n; 467} 468 469static int s526_ao_insn_write(struct comedi_device *dev, 470 struct comedi_subdevice *s, 471 struct comedi_insn *insn, 472 unsigned int *data) 473{ 474 unsigned int chan = CR_CHAN(insn->chanspec); 475 unsigned int val = s->readback[chan]; 476 int i; 477 478 outw(chan << 1, dev->iobase + REG_DAC); 479 480 for (i = 0; i < insn->n; i++) { 481 val = data[i]; 482 outw(val, dev->iobase + REG_ADD); 483 /* starts the D/A conversion */ 484 outw((chan << 1) | 1, dev->iobase + REG_DAC); 485 } 486 s->readback[chan] = val; 487 488 return insn->n; 489} 490 491static int s526_dio_insn_bits(struct comedi_device *dev, 492 struct comedi_subdevice *s, 493 struct comedi_insn *insn, 494 unsigned int *data) 495{ 496 if (comedi_dio_update_state(s, data)) 497 outw(s->state, dev->iobase + REG_DIO); 498 499 data[1] = inw(dev->iobase + REG_DIO) & 0xff; 500 501 return insn->n; 502} 503 504static int s526_dio_insn_config(struct comedi_device *dev, 505 struct comedi_subdevice *s, 506 struct comedi_insn *insn, 507 unsigned int *data) 508{ 509 unsigned int chan = CR_CHAN(insn->chanspec); 510 unsigned int mask; 511 int ret; 512 513 if (chan < 4) 514 mask = 0x0f; 515 else 516 mask = 0xf0; 517 518 ret = comedi_dio_insn_config(dev, s, insn, data, mask); 519 if (ret) 520 return ret; 521 522 /* bit 10/11 set the group 1/2's mode */ 523 if (s->io_bits & 0x0f) 524 s->state |= (1 << 10); 525 else 526 s->state &= ~(1 << 10); 527 if (s->io_bits & 0xf0) 528 s->state |= (1 << 11); 529 else 530 s->state &= ~(1 << 11); 531 532 outw(s->state, dev->iobase + REG_DIO); 533 534 return insn->n; 535} 536 537static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) 538{ 539 struct s526_private *devpriv; 540 struct comedi_subdevice *s; 541 int ret; 542 543 ret = comedi_request_region(dev, it->options[0], 0x40); 544 if (ret) 545 return ret; 546 547 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 548 if (!devpriv) 549 return -ENOMEM; 550 551 ret = comedi_alloc_subdevices(dev, 4); 552 if (ret) 553 return ret; 554 555 s = &dev->subdevices[0]; 556 /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ 557 s->type = COMEDI_SUBD_COUNTER; 558 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; 559 s->n_chan = 4; 560 s->maxdata = 0x00ffffff; /* 24 bit counter */ 561 s->insn_read = s526_gpct_rinsn; 562 s->insn_config = s526_gpct_insn_config; 563 s->insn_write = s526_gpct_winsn; 564 565 s = &dev->subdevices[1]; 566 /* analog input subdevice */ 567 s->type = COMEDI_SUBD_AI; 568 s->subdev_flags = SDF_READABLE | SDF_DIFF; 569 /* channels 0 to 7 are the regular differential inputs */ 570 /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ 571 s->n_chan = 10; 572 s->maxdata = 0xffff; 573 s->range_table = &range_bipolar10; 574 s->len_chanlist = 16; 575 s->insn_read = s526_ai_rinsn; 576 s->insn_config = s526_ai_insn_config; 577 578 s = &dev->subdevices[2]; 579 /* analog output subdevice */ 580 s->type = COMEDI_SUBD_AO; 581 s->subdev_flags = SDF_WRITABLE; 582 s->n_chan = 4; 583 s->maxdata = 0xffff; 584 s->range_table = &range_bipolar10; 585 s->insn_write = s526_ao_insn_write; 586 s->insn_read = comedi_readback_insn_read; 587 588 ret = comedi_alloc_subdev_readback(s); 589 if (ret) 590 return ret; 591 592 s = &dev->subdevices[3]; 593 /* digital i/o subdevice */ 594 s->type = COMEDI_SUBD_DIO; 595 s->subdev_flags = SDF_READABLE | SDF_WRITABLE; 596 s->n_chan = 8; 597 s->maxdata = 1; 598 s->range_table = &range_digital; 599 s->insn_bits = s526_dio_insn_bits; 600 s->insn_config = s526_dio_insn_config; 601 602 return 0; 603} 604 605static struct comedi_driver s526_driver = { 606 .driver_name = "s526", 607 .module = THIS_MODULE, 608 .attach = s526_attach, 609 .detach = comedi_legacy_detach, 610}; 611module_comedi_driver(s526_driver); 612 613MODULE_AUTHOR("Comedi http://www.comedi.org"); 614MODULE_DESCRIPTION("Comedi low-level driver"); 615MODULE_LICENSE("GPL"); 616