1/* 2 comedi/drivers/cb_pcimdas.c 3 Comedi driver for Computer Boards PCIM-DAS1602/16 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: cb_pcimdas 20Description: Measurement Computing PCI Migration series boards 21Devices: [ComputerBoards] PCIM-DAS1602/16 (cb_pcimdas) 22Author: Richard Bytheway 23Updated: Wed, 13 Nov 2002 12:34:56 +0000 24Status: experimental 25 26Written to support the PCIM-DAS1602/16 on a 2.4 series kernel. 27 28Configuration Options: 29 [0] - PCI bus number 30 [1] - PCI slot number 31 32Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org). 33Only supports DIO, AO and simple AI in it's present form. 34No interrupts, multi channel or FIFO AI, 35although the card looks like it could support this. 36See http://www.mccdaq.com/PDFs/Manuals/pcim-das1602-16.pdf for more details. 37*/ 38 39#include <linux/module.h> 40#include <linux/pci.h> 41#include <linux/interrupt.h> 42 43#include "../comedidev.h" 44 45#include "plx9052.h" 46#include "8255.h" 47 48/* Registers for the PCIM-DAS1602/16 */ 49 50/* DAC Offsets */ 51#define ADC_TRIG 0 52#define DAC0_OFFSET 2 53#define DAC1_OFFSET 4 54 55/* AI and Counter Constants */ 56#define MUX_LIMITS 0 57#define MAIN_CONN_DIO 1 58#define ADC_STAT 2 59#define ADC_CONV_STAT 3 60#define ADC_INT 4 61#define ADC_PACER 5 62#define BURST_MODE 6 63#define PROG_GAIN 7 64#define CLK8254_1_DATA 8 65#define CLK8254_2_DATA 9 66#define CLK8254_3_DATA 10 67#define CLK8254_CONTROL 11 68#define USER_COUNTER 12 69#define RESID_COUNT_H 13 70#define RESID_COUNT_L 14 71 72/* 73 * this structure is for data unique to this hardware driver. If 74 * several hardware drivers keep similar information in this structure, 75 * feel free to suggest moving the variable to the struct comedi_device 76 * struct. 77 */ 78struct cb_pcimdas_private { 79 /* base addresses */ 80 unsigned long daqio; 81 unsigned long BADR3; 82}; 83 84static int cb_pcimdas_ai_eoc(struct comedi_device *dev, 85 struct comedi_subdevice *s, 86 struct comedi_insn *insn, 87 unsigned long context) 88{ 89 struct cb_pcimdas_private *devpriv = dev->private; 90 unsigned int status; 91 92 status = inb(devpriv->BADR3 + 2); 93 if ((status & 0x80) == 0) 94 return 0; 95 return -EBUSY; 96} 97 98static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, 99 struct comedi_subdevice *s, 100 struct comedi_insn *insn, unsigned int *data) 101{ 102 struct cb_pcimdas_private *devpriv = dev->private; 103 int n; 104 unsigned int d; 105 int chan = CR_CHAN(insn->chanspec); 106 unsigned short chanlims; 107 int maxchans; 108 int ret; 109 110 /* only support sw initiated reads from a single channel */ 111 112 /* check channel number */ 113 if ((inb(devpriv->BADR3 + 2) & 0x20) == 0) /* differential mode */ 114 maxchans = s->n_chan / 2; 115 else 116 maxchans = s->n_chan; 117 118 if (chan > (maxchans - 1)) 119 return -ETIMEDOUT; /* *** Wrong error code. Fixme. */ 120 121 /* configure for sw initiated read */ 122 d = inb(devpriv->BADR3 + 5); 123 if ((d & 0x03) > 0) { /* only reset if needed. */ 124 d = d & 0xfd; 125 outb(d, devpriv->BADR3 + 5); 126 } 127 128 /* set bursting off, conversions on */ 129 outb(0x01, devpriv->BADR3 + 6); 130 131 /* set range to 10V. UP/BP is controlled by a switch on the board */ 132 outb(0x00, devpriv->BADR3 + 7); 133 134 /* 135 * write channel limits to multiplexer, set Low (bits 0-3) and 136 * High (bits 4-7) channels to chan. 137 */ 138 chanlims = chan | (chan << 4); 139 outb(chanlims, devpriv->BADR3 + 0); 140 141 /* convert n samples */ 142 for (n = 0; n < insn->n; n++) { 143 /* trigger conversion */ 144 outw(0, devpriv->daqio + 0); 145 146 /* wait for conversion to end */ 147 ret = comedi_timeout(dev, s, insn, cb_pcimdas_ai_eoc, 0); 148 if (ret) 149 return ret; 150 151 /* read data */ 152 data[n] = inw(devpriv->daqio + 0); 153 } 154 155 /* return the number of samples read/written */ 156 return n; 157} 158 159static int cb_pcimdas_ao_insn_write(struct comedi_device *dev, 160 struct comedi_subdevice *s, 161 struct comedi_insn *insn, 162 unsigned int *data) 163{ 164 struct cb_pcimdas_private *devpriv = dev->private; 165 unsigned int chan = CR_CHAN(insn->chanspec); 166 unsigned int val = s->readback[chan]; 167 unsigned int reg = (chan) ? DAC1_OFFSET : DAC0_OFFSET; 168 int i; 169 170 for (i = 0; i < insn->n; i++) { 171 val = data[i]; 172 outw(val, devpriv->daqio + reg); 173 } 174 s->readback[chan] = val; 175 176 return insn->n; 177} 178 179static int cb_pcimdas_auto_attach(struct comedi_device *dev, 180 unsigned long context_unused) 181{ 182 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 183 struct cb_pcimdas_private *devpriv; 184 struct comedi_subdevice *s; 185 int ret; 186 187 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 188 if (!devpriv) 189 return -ENOMEM; 190 191 ret = comedi_pci_enable(dev); 192 if (ret) 193 return ret; 194 195 devpriv->daqio = pci_resource_start(pcidev, 2); 196 devpriv->BADR3 = pci_resource_start(pcidev, 3); 197 dev->iobase = pci_resource_start(pcidev, 4); 198 199 ret = comedi_alloc_subdevices(dev, 3); 200 if (ret) 201 return ret; 202 203 s = &dev->subdevices[0]; 204 /* dev->read_subdev=s; */ 205 /* analog input subdevice */ 206 s->type = COMEDI_SUBD_AI; 207 s->subdev_flags = SDF_READABLE | SDF_GROUND; 208 s->n_chan = 16; 209 s->maxdata = 0xffff; 210 s->range_table = &range_unknown; 211 s->len_chanlist = 1; /* This is the maximum chanlist length that */ 212 /* the board can handle */ 213 s->insn_read = cb_pcimdas_ai_rinsn; 214 215 s = &dev->subdevices[1]; 216 /* analog output subdevice */ 217 s->type = COMEDI_SUBD_AO; 218 s->subdev_flags = SDF_WRITABLE; 219 s->n_chan = 2; 220 s->maxdata = 0xfff; 221 /* ranges are hardware settable, but not software readable. */ 222 s->range_table = &range_unknown; 223 s->insn_write = cb_pcimdas_ao_insn_write; 224 s->insn_read = comedi_readback_insn_read; 225 226 ret = comedi_alloc_subdev_readback(s); 227 if (ret) 228 return ret; 229 230 s = &dev->subdevices[2]; 231 /* digital i/o subdevice */ 232 ret = subdev_8255_init(dev, s, NULL, 0x00); 233 if (ret) 234 return ret; 235 236 return 0; 237} 238 239static struct comedi_driver cb_pcimdas_driver = { 240 .driver_name = "cb_pcimdas", 241 .module = THIS_MODULE, 242 .auto_attach = cb_pcimdas_auto_attach, 243 .detach = comedi_pci_detach, 244}; 245 246static int cb_pcimdas_pci_probe(struct pci_dev *dev, 247 const struct pci_device_id *id) 248{ 249 return comedi_pci_auto_config(dev, &cb_pcimdas_driver, 250 id->driver_data); 251} 252 253static const struct pci_device_id cb_pcimdas_pci_table[] = { 254 { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0056) }, 255 { 0 } 256}; 257MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table); 258 259static struct pci_driver cb_pcimdas_pci_driver = { 260 .name = "cb_pcimdas", 261 .id_table = cb_pcimdas_pci_table, 262 .probe = cb_pcimdas_pci_probe, 263 .remove = comedi_pci_auto_unconfig, 264}; 265module_comedi_pci_driver(cb_pcimdas_driver, cb_pcimdas_pci_driver); 266 267MODULE_AUTHOR("Comedi http://www.comedi.org"); 268MODULE_DESCRIPTION("Comedi low-level driver"); 269MODULE_LICENSE("GPL"); 270