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 181 /* clear crap */ 182 inb(dev->iobase + DAS08_AI_LSB_REG); 183 inb(dev->iobase + DAS08_AI_MSB_REG); 184 185 /* set multiplexer */ 186 /* lock to prevent race with digital output */ 187 spin_lock(&dev->spinlock); 188 devpriv->do_mux_bits &= ~DAS08_CONTROL_MUX_MASK; 189 devpriv->do_mux_bits |= DAS08_CONTROL_MUX(chan); 190 outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); 191 spin_unlock(&dev->spinlock); 192 193 if (devpriv->pg_gainlist) { 194 /* set gain/range */ 195 range = CR_RANGE(insn->chanspec); 196 outb(devpriv->pg_gainlist[range], 197 dev->iobase + DAS08_GAIN_REG); 198 } 199 200 for (n = 0; n < insn->n; n++) { 201 /* clear over-range bits for 16-bit boards */ 202 if (board->ai_nbits == 16) 203 if (inb(dev->iobase + DAS08_AI_MSB_REG) & 0x80) 204 dev_info(dev->class_dev, "over-range\n"); 205 206 /* trigger conversion */ 207 outb_p(0, dev->iobase + DAS08_AI_TRIG_REG); 208 209 ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0); 210 if (ret) 211 return ret; 212 213 msb = inb(dev->iobase + DAS08_AI_MSB_REG); 214 lsb = inb(dev->iobase + DAS08_AI_LSB_REG); 215 if (board->ai_encoding == das08_encode12) { 216 data[n] = (lsb >> 4) | (msb << 4); 217 } else if (board->ai_encoding == das08_pcm_encode12) { 218 data[n] = (msb << 8) + lsb; 219 } else if (board->ai_encoding == das08_encode16) { 220 /* 221 * "JR" 16-bit boards are sign-magnitude. 222 * 223 * XXX The manual seems to imply that 0 is full-scale 224 * negative and 65535 is full-scale positive, but the 225 * original COMEDI patch to add support for the 226 * DAS08/JR/16 and DAS08/JR/16-AO boards have it 227 * encoded as sign-magnitude. Assume the original 228 * COMEDI code is correct for now. 229 */ 230 unsigned int magnitude = lsb | ((msb & 0x7f) << 8); 231 232 /* 233 * MSB bit 7 is 0 for negative, 1 for positive voltage. 234 * COMEDI 16-bit bipolar data value for 0V is 0x8000. 235 */ 236 if (msb & 0x80) 237 data[n] = BIT(15) + magnitude; 238 else 239 data[n] = BIT(15) - magnitude; 240 } else { 241 dev_err(dev->class_dev, "bug! unknown ai encoding\n"); 242 return -1; 243 } 244 } 245 246 return n; 247} 248 249static int das08_di_insn_bits(struct comedi_device *dev, 250 struct comedi_subdevice *s, 251 struct comedi_insn *insn, unsigned int *data) 252{ 253 data[0] = 0; 254 data[1] = DAS08_STATUS_DI(inb(dev->iobase + DAS08_STATUS_REG)); 255 256 return insn->n; 257} 258 259static int das08_do_insn_bits(struct comedi_device *dev, 260 struct comedi_subdevice *s, 261 struct comedi_insn *insn, unsigned int *data) 262{ 263 struct das08_private_struct *devpriv = dev->private; 264 265 if (comedi_dio_update_state(s, data)) { 266 /* prevent race with setting of analog input mux */ 267 spin_lock(&dev->spinlock); 268 devpriv->do_mux_bits &= ~DAS08_CONTROL_DO_MASK; 269 devpriv->do_mux_bits |= DAS08_CONTROL_DO(s->state); 270 outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL_REG); 271 spin_unlock(&dev->spinlock); 272 } 273 274 data[1] = s->state; 275 276 return insn->n; 277} 278 279static int das08jr_di_insn_bits(struct comedi_device *dev, 280 struct comedi_subdevice *s, 281 struct comedi_insn *insn, unsigned int *data) 282{ 283 data[0] = 0; 284 data[1] = inb(dev->iobase + DAS08JR_DI_REG); 285 286 return insn->n; 287} 288 289static int das08jr_do_insn_bits(struct comedi_device *dev, 290 struct comedi_subdevice *s, 291 struct comedi_insn *insn, unsigned int *data) 292{ 293 if (comedi_dio_update_state(s, data)) 294 outb(s->state, dev->iobase + DAS08JR_DO_REG); 295 296 data[1] = s->state; 297 298 return insn->n; 299} 300 301static void das08_ao_set_data(struct comedi_device *dev, 302 unsigned int chan, unsigned int data) 303{ 304 const struct das08_board_struct *board = dev->board_ptr; 305 unsigned char lsb; 306 unsigned char msb; 307 308 lsb = data & 0xff; 309 msb = (data >> 8) & 0xff; 310 if (board->is_jr) { 311 outb(lsb, dev->iobase + DAS08JR_AO_LSB_REG(chan)); 312 outb(msb, dev->iobase + DAS08JR_AO_MSB_REG(chan)); 313 /* load DACs */ 314 inb(dev->iobase + DAS08JR_AO_UPDATE_REG); 315 } else { 316 outb(lsb, dev->iobase + DAS08AOX_AO_LSB_REG(chan)); 317 outb(msb, dev->iobase + DAS08AOX_AO_MSB_REG(chan)); 318 /* load DACs */ 319 inb(dev->iobase + DAS08AOX_AO_UPDATE_REG); 320 } 321} 322 323static int das08_ao_insn_write(struct comedi_device *dev, 324 struct comedi_subdevice *s, 325 struct comedi_insn *insn, 326 unsigned int *data) 327{ 328 unsigned int chan = CR_CHAN(insn->chanspec); 329 unsigned int val = s->readback[chan]; 330 int i; 331 332 for (i = 0; i < insn->n; i++) { 333 val = data[i]; 334 das08_ao_set_data(dev, chan, val); 335 } 336 s->readback[chan] = val; 337 338 return insn->n; 339} 340 341int das08_common_attach(struct comedi_device *dev, unsigned long iobase) 342{ 343 const struct das08_board_struct *board = dev->board_ptr; 344 struct das08_private_struct *devpriv = dev->private; 345 struct comedi_subdevice *s; 346 int ret; 347 int i; 348 349 dev->iobase = iobase; 350 351 dev->board_name = board->name; 352 353 ret = comedi_alloc_subdevices(dev, 6); 354 if (ret) 355 return ret; 356 357 s = &dev->subdevices[0]; 358 /* ai */ 359 if (board->ai_nbits) { 360 s->type = COMEDI_SUBD_AI; 361 /* 362 * XXX some boards actually have differential 363 * inputs instead of single ended. 364 * The driver does nothing with arefs though, 365 * so it's no big deal. 366 */ 367 s->subdev_flags = SDF_READABLE | SDF_GROUND; 368 s->n_chan = 8; 369 s->maxdata = (1 << board->ai_nbits) - 1; 370 s->range_table = das08_ai_lranges[board->ai_pg]; 371 s->insn_read = das08_ai_insn_read; 372 devpriv->pg_gainlist = das08_ai_gainlists[board->ai_pg]; 373 } else { 374 s->type = COMEDI_SUBD_UNUSED; 375 } 376 377 s = &dev->subdevices[1]; 378 /* ao */ 379 if (board->ao_nbits) { 380 s->type = COMEDI_SUBD_AO; 381 s->subdev_flags = SDF_WRITABLE; 382 s->n_chan = 2; 383 s->maxdata = (1 << board->ao_nbits) - 1; 384 s->range_table = &range_bipolar5; 385 s->insn_write = das08_ao_insn_write; 386 387 ret = comedi_alloc_subdev_readback(s); 388 if (ret) 389 return ret; 390 391 /* initialize all channels to 0V */ 392 for (i = 0; i < s->n_chan; i++) { 393 s->readback[i] = s->maxdata / 2; 394 das08_ao_set_data(dev, i, s->readback[i]); 395 } 396 } else { 397 s->type = COMEDI_SUBD_UNUSED; 398 } 399 400 s = &dev->subdevices[2]; 401 /* di */ 402 if (board->di_nchan) { 403 s->type = COMEDI_SUBD_DI; 404 s->subdev_flags = SDF_READABLE; 405 s->n_chan = board->di_nchan; 406 s->maxdata = 1; 407 s->range_table = &range_digital; 408 s->insn_bits = board->is_jr ? das08jr_di_insn_bits : 409 das08_di_insn_bits; 410 } else { 411 s->type = COMEDI_SUBD_UNUSED; 412 } 413 414 s = &dev->subdevices[3]; 415 /* do */ 416 if (board->do_nchan) { 417 s->type = COMEDI_SUBD_DO; 418 s->subdev_flags = SDF_WRITABLE; 419 s->n_chan = board->do_nchan; 420 s->maxdata = 1; 421 s->range_table = &range_digital; 422 s->insn_bits = board->is_jr ? das08jr_do_insn_bits : 423 das08_do_insn_bits; 424 } else { 425 s->type = COMEDI_SUBD_UNUSED; 426 } 427 428 s = &dev->subdevices[4]; 429 /* 8255 */ 430 if (board->i8255_offset != 0) { 431 ret = subdev_8255_io_init(dev, s, board->i8255_offset); 432 if (ret) 433 return ret; 434 } else { 435 s->type = COMEDI_SUBD_UNUSED; 436 } 437 438 /* Counter subdevice (8254) */ 439 s = &dev->subdevices[5]; 440 if (board->i8254_offset) { 441 dev->pacer = 442 comedi_8254_io_alloc(dev->iobase + board->i8254_offset, 443 0, I8254_IO8, 0); 444 if (IS_ERR(dev->pacer)) 445 return PTR_ERR(dev->pacer); 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"); 470