das08.c (13079B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * comedi/drivers/das08.c 4 * comedi module for common DAS08 support (used by ISA/PCI/PCMCIA drivers) 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 8 * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net> 9 * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org> 10 */ 11 12#include <linux/module.h> 13#include <linux/comedi/comedidev.h> 14#include <linux/comedi/comedi_8255.h> 15#include <linux/comedi/comedi_8254.h> 16 17#include "das08.h" 18 19/* 20 * Data format of DAS08_AI_LSB_REG and DAS08_AI_MSB_REG depends on 21 * 'ai_encoding' member of board structure: 22 * 23 * das08_encode12 : DATA[11..4] = MSB[7..0], DATA[3..0] = LSB[7..4]. 24 * das08_pcm_encode12 : DATA[11..8] = MSB[3..0], DATA[7..9] = LSB[7..0]. 25 * das08_encode16 : SIGN = MSB[7], MAGNITUDE[14..8] = MSB[6..0], 26 * MAGNITUDE[7..0] = LSB[7..0]. 27 * SIGN==0 for negative input, SIGN==1 for positive input. 28 * Note: when read a second time after conversion 29 * complete, MSB[7] is an "over-range" bit. 30 */ 31#define DAS08_AI_LSB_REG 0x00 /* (R) AI least significant bits */ 32#define DAS08_AI_MSB_REG 0x01 /* (R) AI most significant bits */ 33#define DAS08_AI_TRIG_REG 0x01 /* (W) AI software trigger */ 34#define DAS08_STATUS_REG 0x02 /* (R) status */ 35#define DAS08_STATUS_AI_BUSY BIT(7) /* AI conversion in progress */ 36/* 37 * The IRQ status bit is set to 1 by a rising edge on the external interrupt 38 * input (which may be jumpered to the pacer output). It is cleared by 39 * setting the INTE control bit to 0. Not present on "JR" boards. 40 */ 41#define DAS08_STATUS_IRQ BIT(3) /* latched interrupt input */ 42/* digital inputs (not "JR" boards) */ 43#define DAS08_STATUS_DI(x) (((x) & 0x70) >> 4) 44#define DAS08_CONTROL_REG 0x02 /* (W) control */ 45/* 46 * Note: The AI multiplexor channel can also be read from status register using 47 * the same mask. 48 */ 49#define DAS08_CONTROL_MUX_MASK 0x7 /* multiplexor channel mask */ 50#define DAS08_CONTROL_MUX(x) ((x) & DAS08_CONTROL_MUX_MASK) /* mux channel */ 51#define DAS08_CONTROL_INTE BIT(3) /* interrupt enable (not "JR" boards) */ 52#define DAS08_CONTROL_DO_MASK 0xf0 /* digital outputs mask (not "JR") */ 53/* digital outputs (not "JR" boards) */ 54#define DAS08_CONTROL_DO(x) (((x) << 4) & DAS08_CONTROL_DO_MASK) 55/* 56 * (R/W) programmable AI gain ("PGx" and "AOx" boards): 57 * + bits 3..0 (R/W) show/set the gain for the current AI mux channel 58 * + bits 6..4 (R) show the current AI mux channel 59 * + bit 7 (R) not unused 60 */ 61#define DAS08_GAIN_REG 0x03 62 63#define DAS08JR_DI_REG 0x03 /* (R) digital inputs ("JR" boards) */ 64#define DAS08JR_DO_REG 0x03 /* (W) digital outputs ("JR" boards) */ 65/* (W) analog output l.s.b. registers for 2 channels ("JR" boards) */ 66#define DAS08JR_AO_LSB_REG(x) ((x) ? 0x06 : 0x04) 67/* (W) analog output m.s.b. registers for 2 channels ("JR" boards) */ 68#define DAS08JR_AO_MSB_REG(x) ((x) ? 0x07 : 0x05) 69/* 70 * (R) update analog outputs ("JR" boards set for simultaneous output) 71 * (same register as digital inputs) 72 */ 73#define DAS08JR_AO_UPDATE_REG 0x03 74 75/* (W) analog output l.s.b. registers for 2 channels ("AOx" boards) */ 76#define DAS08AOX_AO_LSB_REG(x) ((x) ? 0x0a : 0x08) 77/* (W) analog output m.s.b. registers for 2 channels ("AOx" boards) */ 78#define DAS08AOX_AO_MSB_REG(x) ((x) ? 0x0b : 0x09) 79/* 80 * (R) update analog outputs ("AOx" boards set for simultaneous output) 81 * (any of the analog output registers could be used for this) 82 */ 83#define DAS08AOX_AO_UPDATE_REG 0x08 84 85/* gainlist same as _pgx_ below */ 86 87static const struct comedi_lrange das08_pgl_ai_range = { 88 9, { 89 BIP_RANGE(10), 90 BIP_RANGE(5), 91 BIP_RANGE(2.5), 92 BIP_RANGE(1.25), 93 BIP_RANGE(0.625), 94 UNI_RANGE(10), 95 UNI_RANGE(5), 96 UNI_RANGE(2.5), 97 UNI_RANGE(1.25) 98 } 99}; 100 101static const struct comedi_lrange das08_pgh_ai_range = { 102 12, { 103 BIP_RANGE(10), 104 BIP_RANGE(5), 105 BIP_RANGE(1), 106 BIP_RANGE(0.5), 107 BIP_RANGE(0.1), 108 BIP_RANGE(0.05), 109 BIP_RANGE(0.01), 110 BIP_RANGE(0.005), 111 UNI_RANGE(10), 112 UNI_RANGE(1), 113 UNI_RANGE(0.1), 114 UNI_RANGE(0.01) 115 } 116}; 117 118static const struct comedi_lrange das08_pgm_ai_range = { 119 9, { 120 BIP_RANGE(10), 121 BIP_RANGE(5), 122 BIP_RANGE(0.5), 123 BIP_RANGE(0.05), 124 BIP_RANGE(0.01), 125 UNI_RANGE(10), 126 UNI_RANGE(1), 127 UNI_RANGE(0.1), 128 UNI_RANGE(0.01) 129 } 130}; 131 132static const struct comedi_lrange *const das08_ai_lranges[] = { 133 [das08_pg_none] = &range_unknown, 134 [das08_bipolar5] = &range_bipolar5, 135 [das08_pgh] = &das08_pgh_ai_range, 136 [das08_pgl] = &das08_pgl_ai_range, 137 [das08_pgm] = &das08_pgm_ai_range, 138}; 139 140static const int das08_pgh_ai_gainlist[] = { 141 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7 142}; 143static const int das08_pgl_ai_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 }; 144static const int das08_pgm_ai_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 }; 145 146static const int *const das08_ai_gainlists[] = { 147 [das08_pg_none] = NULL, 148 [das08_bipolar5] = NULL, 149 [das08_pgh] = das08_pgh_ai_gainlist, 150 [das08_pgl] = das08_pgl_ai_gainlist, 151 [das08_pgm] = das08_pgm_ai_gainlist, 152}; 153 154static int das08_ai_eoc(struct comedi_device *dev, 155 struct comedi_subdevice *s, 156 struct comedi_insn *insn, 157 unsigned long context) 158{ 159 unsigned int status; 160 161 status = inb(dev->iobase + DAS08_STATUS_REG); 162 if ((status & DAS08_STATUS_AI_BUSY) == 0) 163 return 0; 164 return -EBUSY; 165} 166 167static int das08_ai_insn_read(struct comedi_device *dev, 168 struct comedi_subdevice *s, 169 struct comedi_insn *insn, unsigned int *data) 170{ 171 const struct das08_board_struct *board = dev->board_ptr; 172 struct das08_private_struct *devpriv = dev->private; 173 int n; 174 int chan; 175 int range; 176 int lsb, msb; 177 int ret; 178 179 chan = CR_CHAN(insn->chanspec); 180 range = CR_RANGE(insn->chanspec); 181 182 /* clear crap */ 183 inb(dev->iobase + DAS08_AI_LSB_REG); 184 inb(dev->iobase + DAS08_AI_MSB_REG); 185 186 /* set multiplexer */ 187 /* lock to prevent race with digital output */ 188 spin_lock(&dev->spinlock); 189 devpriv->do_mux_bits &= ~DAS08_CONTROL_MUX_MASK; 190 devpriv->do_mux_bits |= DAS08_CONTROL_MUX(chan); 191 outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); 192 spin_unlock(&dev->spinlock); 193 194 if (devpriv->pg_gainlist) { 195 /* set gain/range */ 196 range = CR_RANGE(insn->chanspec); 197 outb(devpriv->pg_gainlist[range], 198 dev->iobase + DAS08_GAIN_REG); 199 } 200 201 for (n = 0; n < insn->n; n++) { 202 /* clear over-range bits for 16-bit boards */ 203 if (board->ai_nbits == 16) 204 if (inb(dev->iobase + DAS08_AI_MSB_REG) & 0x80) 205 dev_info(dev->class_dev, "over-range\n"); 206 207 /* trigger conversion */ 208 outb_p(0, dev->iobase + DAS08_AI_TRIG_REG); 209 210 ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0); 211 if (ret) 212 return ret; 213 214 msb = inb(dev->iobase + DAS08_AI_MSB_REG); 215 lsb = inb(dev->iobase + DAS08_AI_LSB_REG); 216 if (board->ai_encoding == das08_encode12) { 217 data[n] = (lsb >> 4) | (msb << 4); 218 } else if (board->ai_encoding == das08_pcm_encode12) { 219 data[n] = (msb << 8) + lsb; 220 } else if (board->ai_encoding == das08_encode16) { 221 /* 222 * "JR" 16-bit boards are sign-magnitude. 223 * 224 * XXX The manual seems to imply that 0 is full-scale 225 * negative and 65535 is full-scale positive, but the 226 * original COMEDI patch to add support for the 227 * DAS08/JR/16 and DAS08/JR/16-AO boards have it 228 * encoded as sign-magnitude. Assume the original 229 * COMEDI code is correct for now. 230 */ 231 unsigned int magnitude = lsb | ((msb & 0x7f) << 8); 232 233 /* 234 * MSB bit 7 is 0 for negative, 1 for positive voltage. 235 * COMEDI 16-bit bipolar data value for 0V is 0x8000. 236 */ 237 if (msb & 0x80) 238 data[n] = BIT(15) + magnitude; 239 else 240 data[n] = BIT(15) - magnitude; 241 } else { 242 dev_err(dev->class_dev, "bug! unknown ai encoding\n"); 243 return -1; 244 } 245 } 246 247 return n; 248} 249 250static int das08_di_insn_bits(struct comedi_device *dev, 251 struct comedi_subdevice *s, 252 struct comedi_insn *insn, unsigned int *data) 253{ 254 data[0] = 0; 255 data[1] = DAS08_STATUS_DI(inb(dev->iobase + DAS08_STATUS_REG)); 256 257 return insn->n; 258} 259 260static int das08_do_insn_bits(struct comedi_device *dev, 261 struct comedi_subdevice *s, 262 struct comedi_insn *insn, unsigned int *data) 263{ 264 struct das08_private_struct *devpriv = dev->private; 265 266 if (comedi_dio_update_state(s, data)) { 267 /* prevent race with setting of analog input mux */ 268 spin_lock(&dev->spinlock); 269 devpriv->do_mux_bits &= ~DAS08_CONTROL_DO_MASK; 270 devpriv->do_mux_bits |= DAS08_CONTROL_DO(s->state); 271 outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); 272 spin_unlock(&dev->spinlock); 273 } 274 275 data[1] = s->state; 276 277 return insn->n; 278} 279 280static int das08jr_di_insn_bits(struct comedi_device *dev, 281 struct comedi_subdevice *s, 282 struct comedi_insn *insn, unsigned int *data) 283{ 284 data[0] = 0; 285 data[1] = inb(dev->iobase + DAS08JR_DI_REG); 286 287 return insn->n; 288} 289 290static int das08jr_do_insn_bits(struct comedi_device *dev, 291 struct comedi_subdevice *s, 292 struct comedi_insn *insn, unsigned int *data) 293{ 294 if (comedi_dio_update_state(s, data)) 295 outb(s->state, dev->iobase + DAS08JR_DO_REG); 296 297 data[1] = s->state; 298 299 return insn->n; 300} 301 302static void das08_ao_set_data(struct comedi_device *dev, 303 unsigned int chan, unsigned int data) 304{ 305 const struct das08_board_struct *board = dev->board_ptr; 306 unsigned char lsb; 307 unsigned char msb; 308 309 lsb = data & 0xff; 310 msb = (data >> 8) & 0xff; 311 if (board->is_jr) { 312 outb(lsb, dev->iobase + DAS08JR_AO_LSB_REG(chan)); 313 outb(msb, dev->iobase + DAS08JR_AO_MSB_REG(chan)); 314 /* load DACs */ 315 inb(dev->iobase + DAS08JR_AO_UPDATE_REG); 316 } else { 317 outb(lsb, dev->iobase + DAS08AOX_AO_LSB_REG(chan)); 318 outb(msb, dev->iobase + DAS08AOX_AO_MSB_REG(chan)); 319 /* load DACs */ 320 inb(dev->iobase + DAS08AOX_AO_UPDATE_REG); 321 } 322} 323 324static int das08_ao_insn_write(struct comedi_device *dev, 325 struct comedi_subdevice *s, 326 struct comedi_insn *insn, 327 unsigned int *data) 328{ 329 unsigned int chan = CR_CHAN(insn->chanspec); 330 unsigned int val = s->readback[chan]; 331 int i; 332 333 for (i = 0; i < insn->n; i++) { 334 val = data[i]; 335 das08_ao_set_data(dev, chan, val); 336 } 337 s->readback[chan] = val; 338 339 return insn->n; 340} 341 342int das08_common_attach(struct comedi_device *dev, unsigned long iobase) 343{ 344 const struct das08_board_struct *board = dev->board_ptr; 345 struct das08_private_struct *devpriv = dev->private; 346 struct comedi_subdevice *s; 347 int ret; 348 int i; 349 350 dev->iobase = iobase; 351 352 dev->board_name = board->name; 353 354 ret = comedi_alloc_subdevices(dev, 6); 355 if (ret) 356 return ret; 357 358 s = &dev->subdevices[0]; 359 /* ai */ 360 if (board->ai_nbits) { 361 s->type = COMEDI_SUBD_AI; 362 /* 363 * XXX some boards actually have differential 364 * inputs instead of single ended. 365 * The driver does nothing with arefs though, 366 * so it's no big deal. 367 */ 368 s->subdev_flags = SDF_READABLE | SDF_GROUND; 369 s->n_chan = 8; 370 s->maxdata = (1 << board->ai_nbits) - 1; 371 s->range_table = das08_ai_lranges[board->ai_pg]; 372 s->insn_read = das08_ai_insn_read; 373 devpriv->pg_gainlist = das08_ai_gainlists[board->ai_pg]; 374 } else { 375 s->type = COMEDI_SUBD_UNUSED; 376 } 377 378 s = &dev->subdevices[1]; 379 /* ao */ 380 if (board->ao_nbits) { 381 s->type = COMEDI_SUBD_AO; 382 s->subdev_flags = SDF_WRITABLE; 383 s->n_chan = 2; 384 s->maxdata = (1 << board->ao_nbits) - 1; 385 s->range_table = &range_bipolar5; 386 s->insn_write = das08_ao_insn_write; 387 388 ret = comedi_alloc_subdev_readback(s); 389 if (ret) 390 return ret; 391 392 /* initialize all channels to 0V */ 393 for (i = 0; i < s->n_chan; i++) { 394 s->readback[i] = s->maxdata / 2; 395 das08_ao_set_data(dev, i, s->readback[i]); 396 } 397 } else { 398 s->type = COMEDI_SUBD_UNUSED; 399 } 400 401 s = &dev->subdevices[2]; 402 /* di */ 403 if (board->di_nchan) { 404 s->type = COMEDI_SUBD_DI; 405 s->subdev_flags = SDF_READABLE; 406 s->n_chan = board->di_nchan; 407 s->maxdata = 1; 408 s->range_table = &range_digital; 409 s->insn_bits = board->is_jr ? das08jr_di_insn_bits : 410 das08_di_insn_bits; 411 } else { 412 s->type = COMEDI_SUBD_UNUSED; 413 } 414 415 s = &dev->subdevices[3]; 416 /* do */ 417 if (board->do_nchan) { 418 s->type = COMEDI_SUBD_DO; 419 s->subdev_flags = SDF_WRITABLE; 420 s->n_chan = board->do_nchan; 421 s->maxdata = 1; 422 s->range_table = &range_digital; 423 s->insn_bits = board->is_jr ? das08jr_do_insn_bits : 424 das08_do_insn_bits; 425 } else { 426 s->type = COMEDI_SUBD_UNUSED; 427 } 428 429 s = &dev->subdevices[4]; 430 /* 8255 */ 431 if (board->i8255_offset != 0) { 432 ret = subdev_8255_init(dev, s, NULL, board->i8255_offset); 433 if (ret) 434 return ret; 435 } else { 436 s->type = COMEDI_SUBD_UNUSED; 437 } 438 439 /* Counter subdevice (8254) */ 440 s = &dev->subdevices[5]; 441 if (board->i8254_offset) { 442 dev->pacer = comedi_8254_init(dev->iobase + board->i8254_offset, 443 0, I8254_IO8, 0); 444 if (!dev->pacer) 445 return -ENOMEM; 446 447 comedi_8254_subdevice_init(s, dev->pacer); 448 } else { 449 s->type = COMEDI_SUBD_UNUSED; 450 } 451 452 return 0; 453} 454EXPORT_SYMBOL_GPL(das08_common_attach); 455 456static int __init das08_init(void) 457{ 458 return 0; 459} 460module_init(das08_init); 461 462static void __exit das08_exit(void) 463{ 464} 465module_exit(das08_exit); 466 467MODULE_AUTHOR("Comedi https://www.comedi.org"); 468MODULE_DESCRIPTION("Comedi common DAS08 support module"); 469MODULE_LICENSE("GPL");