Commit | Line | Data |
---|---|---|
086df5b5 IM |
1 | /* |
2 | comedi/drivers/cb_pcidda.c | |
3 | This intends to be a driver for the ComputerBoards / MeasurementComputing | |
4 | PCI-DDA series. | |
5 | ||
6 | Copyright (C) 2001 Ivan Martinez <ivanmr@altavista.com> | |
7 | Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net> | |
8 | ||
9 | COMEDI - Linux Control and Measurement Device Interface | |
10 | Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> | |
11 | ||
12 | This program is free software; you can redistribute it and/or modify | |
13 | it under the terms of the GNU General Public License as published by | |
14 | the Free Software Foundation; either version 2 of the License, or | |
15 | (at your option) any later version. | |
16 | ||
17 | This program is distributed in the hope that it will be useful, | |
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | GNU General Public License for more details. | |
21 | ||
22 | You should have received a copy of the GNU General Public License | |
23 | along with this program; if not, write to the Free Software | |
24 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
25 | ||
26 | */ | |
27 | /* | |
28 | Driver: cb_pcidda | |
29 | Description: MeasurementComputing PCI-DDA series | |
30 | Author: Ivan Martinez <ivanmr@altavista.com>, Frank Mori Hess <fmhess@users.sourceforge.net> | |
31 | Status: Supports 08/16, 04/16, 02/16, 08/12, 04/12, and 02/12 | |
32 | Devices: [Measurement Computing] PCI-DDA08/12 (cb_pcidda), PCI-DDA04/12, | |
33 | PCI-DDA02/12, PCI-DDA08/16, PCI-DDA04/16, PCI-DDA02/16 | |
34 | ||
35 | Configuration options: | |
36 | [0] - PCI bus of device (optional) | |
37 | [1] - PCI slot of device (optional) | |
38 | If bus/slot is not specified, the first available PCI | |
39 | device will be used. | |
40 | ||
41 | Only simple analog output writing is supported. | |
42 | ||
43 | So far it has only been tested with: | |
44 | - PCI-DDA08/12 | |
45 | Please report success/failure with other different cards to | |
46 | <comedi@comedi.org>. | |
47 | */ | |
48 | ||
49 | #include "../comedidev.h" | |
50 | ||
086df5b5 IM |
51 | #include "8255.h" |
52 | ||
3c5510ba RKM |
53 | /* PCI vendor number of ComputerBoards */ |
54 | #define PCI_VENDOR_ID_CB 0x1307 | |
2696fb57 | 55 | #define EEPROM_SIZE 128 /* number of entries in eeprom */ |
3c5510ba RKM |
56 | /* maximum number of ao channels for supported boards */ |
57 | #define MAX_AO_CHANNELS 8 | |
086df5b5 IM |
58 | |
59 | /* PCI-DDA base addresses */ | |
60 | #define DIGITALIO_BADRINDEX 2 | |
2696fb57 | 61 | /* DIGITAL I/O is pci_dev->resource[2] */ |
086df5b5 | 62 | #define DIGITALIO_SIZE 8 |
2696fb57 | 63 | /* DIGITAL I/O uses 8 I/O port addresses */ |
086df5b5 | 64 | #define DAC_BADRINDEX 3 |
2696fb57 | 65 | /* DAC is pci_dev->resource[3] */ |
086df5b5 IM |
66 | |
67 | /* Digital I/O registers */ | |
2696fb57 | 68 | #define PORT1A 0 /* PORT 1A DATA */ |
086df5b5 | 69 | |
2696fb57 | 70 | #define PORT1B 1 /* PORT 1B DATA */ |
086df5b5 | 71 | |
2696fb57 | 72 | #define PORT1C 2 /* PORT 1C DATA */ |
086df5b5 | 73 | |
2696fb57 | 74 | #define CONTROL1 3 /* CONTROL REGISTER 1 */ |
086df5b5 | 75 | |
2696fb57 | 76 | #define PORT2A 4 /* PORT 2A DATA */ |
086df5b5 | 77 | |
2696fb57 | 78 | #define PORT2B 5 /* PORT 2B DATA */ |
086df5b5 | 79 | |
2696fb57 | 80 | #define PORT2C 6 /* PORT 2C DATA */ |
086df5b5 | 81 | |
2696fb57 | 82 | #define CONTROL2 7 /* CONTROL REGISTER 2 */ |
086df5b5 IM |
83 | |
84 | /* DAC registers */ | |
2696fb57 BP |
85 | #define DACONTROL 0 /* D/A CONTROL REGISTER */ |
86 | #define SU 0000001 /* Simultaneous update enabled */ | |
87 | #define NOSU 0000000 /* Simultaneous update disabled */ | |
88 | #define ENABLEDAC 0000002 /* Enable specified DAC */ | |
89 | #define DISABLEDAC 0000000 /* Disable specified DAC */ | |
90 | #define RANGE2V5 0000000 /* 2.5V */ | |
91 | #define RANGE5V 0000200 /* 5V */ | |
92 | #define RANGE10V 0000300 /* 10V */ | |
93 | #define UNIP 0000400 /* Unipolar outputs */ | |
94 | #define BIP 0000000 /* Bipolar outputs */ | |
95 | ||
96 | #define DACALIBRATION1 4 /* D/A CALIBRATION REGISTER 1 */ | |
97 | /* write bits */ | |
3c5510ba RKM |
98 | /* serial data input for eeprom, caldacs, reference dac */ |
99 | #define SERIAL_IN_BIT 0x1 | |
086df5b5 IM |
100 | #define CAL_CHANNEL_MASK (0x7 << 1) |
101 | #define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK) | |
2696fb57 | 102 | /* read bits */ |
086df5b5 | 103 | #define CAL_COUNTER_MASK 0x1f |
3c5510ba RKM |
104 | /* calibration counter overflow status bit */ |
105 | #define CAL_COUNTER_OVERFLOW_BIT 0x20 | |
106 | /* analog output is less than reference dac voltage */ | |
107 | #define AO_BELOW_REF_BIT 0x40 | |
2696fb57 | 108 | #define SERIAL_OUT_BIT 0x80 /* serial data out, for reading from eeprom */ |
086df5b5 | 109 | |
2696fb57 BP |
110 | #define DACALIBRATION2 6 /* D/A CALIBRATION REGISTER 2 */ |
111 | #define SELECT_EEPROM_BIT 0x1 /* send serial data in to eeprom */ | |
3c5510ba RKM |
112 | /* don't send serial data to MAX542 reference dac */ |
113 | #define DESELECT_REF_DAC_BIT 0x2 | |
114 | /* don't send serial data to caldac n */ | |
115 | #define DESELECT_CALDAC_BIT(n) (0x4 << (n)) | |
116 | /* manual says to set this bit with no explanation */ | |
117 | #define DUMMY_BIT 0x40 | |
086df5b5 | 118 | |
2696fb57 | 119 | #define DADATA 8 /* FIRST D/A DATA REGISTER (0) */ |
086df5b5 | 120 | |
9ced1de6 | 121 | static const struct comedi_lrange cb_pcidda_ranges = { |
086df5b5 IM |
122 | 6, |
123 | { | |
0a85b6f0 MT |
124 | BIP_RANGE(10), |
125 | BIP_RANGE(5), | |
126 | BIP_RANGE(2.5), | |
127 | UNI_RANGE(10), | |
128 | UNI_RANGE(5), | |
129 | UNI_RANGE(2.5), | |
130 | } | |
086df5b5 IM |
131 | }; |
132 | ||
133 | /* | |
134 | * Board descriptions for two imaginary boards. Describing the | |
135 | * boards in this way is optional, and completely driver-dependent. | |
136 | * Some drivers use arrays such as this, other do not. | |
137 | */ | |
1657e325 | 138 | struct cb_pcidda_board { |
086df5b5 | 139 | const char *name; |
2696fb57 BP |
140 | char status; /* Driver status: */ |
141 | ||
142 | /* | |
143 | * 0 - tested | |
144 | * 1 - manual read, not tested | |
145 | * 2 - manual not read | |
146 | */ | |
147 | ||
086df5b5 IM |
148 | unsigned short device_id; |
149 | int ao_chans; | |
150 | int ao_bits; | |
9ced1de6 | 151 | const struct comedi_lrange *ranges; |
1657e325 | 152 | }; |
2696fb57 | 153 | |
1657e325 | 154 | static const struct cb_pcidda_board cb_pcidda_boards[] = { |
086df5b5 | 155 | { |
0a85b6f0 MT |
156 | .name = "pci-dda02/12", |
157 | .status = 1, | |
158 | .device_id = 0x20, | |
159 | .ao_chans = 2, | |
160 | .ao_bits = 12, | |
161 | .ranges = &cb_pcidda_ranges, | |
162 | }, | |
086df5b5 | 163 | { |
0a85b6f0 MT |
164 | .name = "pci-dda04/12", |
165 | .status = 1, | |
166 | .device_id = 0x21, | |
167 | .ao_chans = 4, | |
168 | .ao_bits = 12, | |
169 | .ranges = &cb_pcidda_ranges, | |
170 | }, | |
086df5b5 | 171 | { |
0a85b6f0 MT |
172 | .name = "pci-dda08/12", |
173 | .status = 0, | |
174 | .device_id = 0x22, | |
175 | .ao_chans = 8, | |
176 | .ao_bits = 12, | |
177 | .ranges = &cb_pcidda_ranges, | |
178 | }, | |
086df5b5 | 179 | { |
0a85b6f0 MT |
180 | .name = "pci-dda02/16", |
181 | .status = 2, | |
182 | .device_id = 0x23, | |
183 | .ao_chans = 2, | |
184 | .ao_bits = 16, | |
185 | .ranges = &cb_pcidda_ranges, | |
186 | }, | |
086df5b5 | 187 | { |
0a85b6f0 MT |
188 | .name = "pci-dda04/16", |
189 | .status = 2, | |
190 | .device_id = 0x24, | |
191 | .ao_chans = 4, | |
192 | .ao_bits = 16, | |
193 | .ranges = &cb_pcidda_ranges, | |
194 | }, | |
086df5b5 | 195 | { |
0a85b6f0 MT |
196 | .name = "pci-dda08/16", |
197 | .status = 0, | |
198 | .device_id = 0x25, | |
199 | .ao_chans = 8, | |
200 | .ao_bits = 16, | |
201 | .ranges = &cb_pcidda_ranges, | |
202 | }, | |
086df5b5 IM |
203 | }; |
204 | ||
086df5b5 IM |
205 | /* |
206 | * Useful for shorthand access to the particular board structure | |
207 | */ | |
1657e325 | 208 | #define thisboard ((const struct cb_pcidda_board *)dev->board_ptr) |
086df5b5 | 209 | |
3c5510ba RKM |
210 | /* |
211 | * this structure is for data unique to this hardware driver. If | |
212 | * several hardware drivers keep similar information in this structure, | |
213 | * feel free to suggest moving the variable to the struct comedi_device | |
214 | * struct. | |
215 | */ | |
cc7bb61e | 216 | struct cb_pcidda_private { |
086df5b5 IM |
217 | int data; |
218 | ||
219 | /* would be useful for a PCI device */ | |
220 | struct pci_dev *pci_dev; | |
221 | ||
222 | unsigned long digitalio; | |
223 | unsigned long dac; | |
2696fb57 BP |
224 | |
225 | /* unsigned long control_status; */ | |
226 | /* unsigned long adc_fifo; */ | |
227 | ||
3c5510ba RKM |
228 | /* bits last written to da calibration register 1 */ |
229 | unsigned int dac_cal1_bits; | |
230 | /* current range settings for output channels */ | |
231 | unsigned int ao_range[MAX_AO_CHANNELS]; | |
2696fb57 | 232 | u16 eeprom_data[EEPROM_SIZE]; /* software copy of board's eeprom */ |
cc7bb61e | 233 | }; |
086df5b5 IM |
234 | |
235 | /* | |
236 | * most drivers define the following macro to make it easy to | |
237 | * access the private structure. | |
238 | */ | |
cc7bb61e | 239 | #define devpriv ((struct cb_pcidda_private *)dev->private) |
086df5b5 | 240 | |
2696fb57 | 241 | /* static int cb_pcidda_ai_rinsn(struct comedi_device *dev,struct comedi_subdevice *s,struct comedi_insn *insn,unsigned int *data); */ |
0a85b6f0 MT |
242 | static int cb_pcidda_ao_winsn(struct comedi_device *dev, |
243 | struct comedi_subdevice *s, | |
244 | struct comedi_insn *insn, unsigned int *data); | |
2696fb57 BP |
245 | |
246 | /* static int cb_pcidda_ai_cmd(struct comedi_device *dev, struct *comedi_subdevice *s);*/ | |
247 | /* static int cb_pcidda_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd); */ | |
248 | /* static int cb_pcidda_ns_to_timer(unsigned int *ns,int *round); */ | |
249 | ||
da91b269 BP |
250 | static unsigned int cb_pcidda_serial_in(struct comedi_device *dev); |
251 | static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value, | |
0a85b6f0 | 252 | unsigned int num_bits); |
da91b269 | 253 | static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, |
0a85b6f0 | 254 | unsigned int address); |
da91b269 | 255 | static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel, |
0a85b6f0 | 256 | unsigned int range); |
086df5b5 | 257 | |
086df5b5 IM |
258 | /* |
259 | * Attach is called by the Comedi core to configure the driver | |
260 | * for a particular board. | |
261 | */ | |
0a85b6f0 MT |
262 | static int cb_pcidda_attach(struct comedi_device *dev, |
263 | struct comedi_devconfig *it) | |
086df5b5 | 264 | { |
34c43922 | 265 | struct comedi_subdevice *s; |
20fb2280 | 266 | struct pci_dev *pcidev = NULL; |
086df5b5 | 267 | int index; |
8b6c5694 | 268 | int ret; |
086df5b5 IM |
269 | |
270 | /* | |
271 | * Allocate the private structure area. | |
272 | */ | |
cc7bb61e | 273 | if (alloc_private(dev, sizeof(struct cb_pcidda_private)) < 0) |
086df5b5 IM |
274 | return -ENOMEM; |
275 | ||
276 | /* | |
277 | * Probe the device to determine what device in the series it is. | |
278 | */ | |
086df5b5 | 279 | |
20fb2280 | 280 | for_each_pci_dev(pcidev) { |
086df5b5 IM |
281 | if (pcidev->vendor == PCI_VENDOR_ID_CB) { |
282 | if (it->options[0] || it->options[1]) { | |
283 | if (pcidev->bus->number != it->options[0] || | |
0a85b6f0 | 284 | PCI_SLOT(pcidev->devfn) != it->options[1]) { |
086df5b5 IM |
285 | continue; |
286 | } | |
287 | } | |
821e67a1 | 288 | for (index = 0; index < ARRAY_SIZE(cb_pcidda_boards); index++) { |
086df5b5 | 289 | if (cb_pcidda_boards[index].device_id == |
0a85b6f0 | 290 | pcidev->device) { |
086df5b5 IM |
291 | goto found; |
292 | } | |
293 | } | |
294 | } | |
295 | } | |
296 | if (!pcidev) { | |
143c128c | 297 | dev_err(dev->hw_dev, "Not a ComputerBoards/MeasurementComputing card on requested position\n"); |
086df5b5 IM |
298 | return -EIO; |
299 | } | |
0a85b6f0 | 300 | found: |
086df5b5 IM |
301 | devpriv->pci_dev = pcidev; |
302 | dev->board_ptr = cb_pcidda_boards + index; | |
2696fb57 | 303 | /* "thisboard" macro can be used from here. */ |
143c128c RM |
304 | dev_dbg(dev->hw_dev, "Found %s at requested position\n", |
305 | thisboard->name); | |
086df5b5 IM |
306 | |
307 | /* | |
308 | * Enable PCI device and request regions. | |
309 | */ | |
310 | if (comedi_pci_enable(pcidev, thisboard->name)) { | |
143c128c | 311 | dev_err(dev->hw_dev, "cb_pcidda: failed to enable PCI device and request regions\n"); |
086df5b5 IM |
312 | return -EIO; |
313 | } | |
314 | ||
315 | /* | |
316 | * Allocate the I/O ports. | |
317 | */ | |
318 | devpriv->digitalio = | |
0a85b6f0 | 319 | pci_resource_start(devpriv->pci_dev, DIGITALIO_BADRINDEX); |
086df5b5 IM |
320 | devpriv->dac = pci_resource_start(devpriv->pci_dev, DAC_BADRINDEX); |
321 | ||
322 | /* | |
323 | * Warn about the status of the driver. | |
324 | */ | |
325 | if (thisboard->status == 2) | |
0a85b6f0 MT |
326 | printk |
327 | ("WARNING: DRIVER FOR THIS BOARD NOT CHECKED WITH MANUAL. " | |
328 | "WORKS ASSUMING FULL COMPATIBILITY WITH PCI-DDA08/12. " | |
329 | "PLEASE REPORT USAGE TO <ivanmr@altavista.com>.\n"); | |
086df5b5 IM |
330 | |
331 | /* | |
332 | * Initialize dev->board_name. | |
333 | */ | |
334 | dev->board_name = thisboard->name; | |
335 | ||
8b6c5694 HS |
336 | ret = comedi_alloc_subdevices(dev, 3); |
337 | if (ret) | |
338 | return ret; | |
086df5b5 IM |
339 | |
340 | s = dev->subdevices + 0; | |
341 | /* analog output subdevice */ | |
342 | s->type = COMEDI_SUBD_AO; | |
343 | s->subdev_flags = SDF_WRITABLE; | |
344 | s->n_chan = thisboard->ao_chans; | |
345 | s->maxdata = (1 << thisboard->ao_bits) - 1; | |
346 | s->range_table = thisboard->ranges; | |
347 | s->insn_write = cb_pcidda_ao_winsn; | |
086df5b5 | 348 | |
2696fb57 BP |
349 | /* s->subdev_flags |= SDF_CMD_READ; */ |
350 | /* s->do_cmd = cb_pcidda_ai_cmd; */ | |
351 | /* s->do_cmdtest = cb_pcidda_ai_cmdtest; */ | |
352 | ||
353 | /* two 8255 digital io subdevices */ | |
086df5b5 IM |
354 | s = dev->subdevices + 1; |
355 | subdev_8255_init(dev, s, NULL, devpriv->digitalio); | |
356 | s = dev->subdevices + 2; | |
357 | subdev_8255_init(dev, s, NULL, devpriv->digitalio + PORT2A); | |
358 | ||
143c128c | 359 | dev_dbg(dev->hw_dev, "eeprom:\n"); |
086df5b5 IM |
360 | for (index = 0; index < EEPROM_SIZE; index++) { |
361 | devpriv->eeprom_data[index] = cb_pcidda_read_eeprom(dev, index); | |
3c5510ba RKM |
362 | dev_dbg(dev->hw_dev, "%i:0x%x\n", index, |
363 | devpriv->eeprom_data[index]); | |
086df5b5 | 364 | } |
086df5b5 | 365 | |
2696fb57 | 366 | /* set calibrations dacs */ |
086df5b5 IM |
367 | for (index = 0; index < thisboard->ao_chans; index++) |
368 | cb_pcidda_calibrate(dev, index, devpriv->ao_range[index]); | |
369 | ||
370 | return 1; | |
371 | } | |
372 | ||
484ecc95 | 373 | static void cb_pcidda_detach(struct comedi_device *dev) |
086df5b5 | 374 | { |
086df5b5 IM |
375 | if (devpriv) { |
376 | if (devpriv->pci_dev) { | |
20db7d7d | 377 | if (devpriv->dac) |
086df5b5 | 378 | comedi_pci_disable(devpriv->pci_dev); |
086df5b5 IM |
379 | pci_dev_put(devpriv->pci_dev); |
380 | } | |
381 | } | |
086df5b5 IM |
382 | if (dev->subdevices) { |
383 | subdev_8255_cleanup(dev, dev->subdevices + 1); | |
384 | subdev_8255_cleanup(dev, dev->subdevices + 2); | |
385 | } | |
086df5b5 IM |
386 | } |
387 | ||
388 | /* | |
389 | * I will program this later... ;-) | |
390 | */ | |
391 | #if 0 | |
0a85b6f0 MT |
392 | static int cb_pcidda_ai_cmd(struct comedi_device *dev, |
393 | struct comedi_subdevice *s) | |
086df5b5 IM |
394 | { |
395 | printk("cb_pcidda_ai_cmd\n"); | |
396 | printk("subdev: %d\n", cmd->subdev); | |
397 | printk("flags: %d\n", cmd->flags); | |
398 | printk("start_src: %d\n", cmd->start_src); | |
399 | printk("start_arg: %d\n", cmd->start_arg); | |
400 | printk("scan_begin_src: %d\n", cmd->scan_begin_src); | |
401 | printk("convert_src: %d\n", cmd->convert_src); | |
402 | printk("convert_arg: %d\n", cmd->convert_arg); | |
403 | printk("scan_end_src: %d\n", cmd->scan_end_src); | |
404 | printk("scan_end_arg: %d\n", cmd->scan_end_arg); | |
405 | printk("stop_src: %d\n", cmd->stop_src); | |
406 | printk("stop_arg: %d\n", cmd->stop_arg); | |
407 | printk("chanlist_len: %d\n", cmd->chanlist_len); | |
408 | } | |
409 | #endif | |
410 | ||
411 | #if 0 | |
0a85b6f0 MT |
412 | static int cb_pcidda_ai_cmdtest(struct comedi_device *dev, |
413 | struct comedi_subdevice *s, | |
414 | struct comedi_cmd *cmd) | |
086df5b5 IM |
415 | { |
416 | int err = 0; | |
417 | int tmp; | |
418 | ||
419 | /* cmdtest tests a particular command to see if it is valid. | |
420 | * Using the cmdtest ioctl, a user can create a valid cmd | |
421 | * and then have it executes by the cmd ioctl. | |
422 | * | |
423 | * cmdtest returns 1,2,3,4 or 0, depending on which tests | |
424 | * the command passes. */ | |
425 | ||
426 | /* step 1: make sure trigger sources are trivially valid */ | |
427 | ||
428 | tmp = cmd->start_src; | |
429 | cmd->start_src &= TRIG_NOW; | |
430 | if (!cmd->start_src || tmp != cmd->start_src) | |
431 | err++; | |
432 | ||
433 | tmp = cmd->scan_begin_src; | |
434 | cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; | |
435 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
436 | err++; | |
437 | ||
438 | tmp = cmd->convert_src; | |
439 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
440 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
441 | err++; | |
442 | ||
443 | tmp = cmd->scan_end_src; | |
444 | cmd->scan_end_src &= TRIG_COUNT; | |
445 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
446 | err++; | |
447 | ||
448 | tmp = cmd->stop_src; | |
449 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
450 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
451 | err++; | |
452 | ||
453 | if (err) | |
454 | return 1; | |
455 | ||
3c5510ba RKM |
456 | /* |
457 | * step 2: make sure trigger sources are unique and mutually | |
458 | * compatible | |
459 | */ | |
086df5b5 | 460 | |
828684f9 | 461 | /* note that mutual compatibility is not an issue here */ |
086df5b5 | 462 | if (cmd->scan_begin_src != TRIG_TIMER |
0a85b6f0 | 463 | && cmd->scan_begin_src != TRIG_EXT) |
086df5b5 IM |
464 | err++; |
465 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
466 | err++; | |
467 | if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) | |
468 | err++; | |
469 | ||
470 | if (err) | |
471 | return 2; | |
472 | ||
473 | /* step 3: make sure arguments are trivially compatible */ | |
474 | ||
475 | if (cmd->start_arg != 0) { | |
476 | cmd->start_arg = 0; | |
477 | err++; | |
478 | } | |
479 | #define MAX_SPEED 10000 /* in nanoseconds */ | |
480 | #define MIN_SPEED 1000000000 /* in nanoseconds */ | |
481 | ||
482 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
483 | if (cmd->scan_begin_arg < MAX_SPEED) { | |
484 | cmd->scan_begin_arg = MAX_SPEED; | |
485 | err++; | |
486 | } | |
487 | if (cmd->scan_begin_arg > MIN_SPEED) { | |
488 | cmd->scan_begin_arg = MIN_SPEED; | |
489 | err++; | |
490 | } | |
491 | } else { | |
492 | /* external trigger */ | |
493 | /* should be level/edge, hi/lo specification here */ | |
494 | /* should specify multiple external triggers */ | |
495 | if (cmd->scan_begin_arg > 9) { | |
496 | cmd->scan_begin_arg = 9; | |
497 | err++; | |
498 | } | |
499 | } | |
500 | if (cmd->convert_src == TRIG_TIMER) { | |
501 | if (cmd->convert_arg < MAX_SPEED) { | |
502 | cmd->convert_arg = MAX_SPEED; | |
503 | err++; | |
504 | } | |
505 | if (cmd->convert_arg > MIN_SPEED) { | |
506 | cmd->convert_arg = MIN_SPEED; | |
507 | err++; | |
508 | } | |
509 | } else { | |
510 | /* external trigger */ | |
511 | /* see above */ | |
512 | if (cmd->convert_arg > 9) { | |
513 | cmd->convert_arg = 9; | |
514 | err++; | |
515 | } | |
516 | } | |
517 | ||
518 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
519 | cmd->scan_end_arg = cmd->chanlist_len; | |
520 | err++; | |
521 | } | |
522 | if (cmd->stop_src == TRIG_COUNT) { | |
523 | if (cmd->stop_arg > 0x00ffffff) { | |
524 | cmd->stop_arg = 0x00ffffff; | |
525 | err++; | |
526 | } | |
527 | } else { | |
528 | /* TRIG_NONE */ | |
529 | if (cmd->stop_arg != 0) { | |
530 | cmd->stop_arg = 0; | |
531 | err++; | |
532 | } | |
533 | } | |
534 | ||
535 | if (err) | |
536 | return 3; | |
537 | ||
538 | /* step 4: fix up any arguments */ | |
539 | ||
540 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
541 | tmp = cmd->scan_begin_arg; | |
542 | cb_pcidda_ns_to_timer(&cmd->scan_begin_arg, | |
0a85b6f0 | 543 | cmd->flags & TRIG_ROUND_MASK); |
086df5b5 IM |
544 | if (tmp != cmd->scan_begin_arg) |
545 | err++; | |
546 | } | |
547 | if (cmd->convert_src == TRIG_TIMER) { | |
548 | tmp = cmd->convert_arg; | |
549 | cb_pcidda_ns_to_timer(&cmd->convert_arg, | |
0a85b6f0 | 550 | cmd->flags & TRIG_ROUND_MASK); |
086df5b5 IM |
551 | if (tmp != cmd->convert_arg) |
552 | err++; | |
553 | if (cmd->scan_begin_src == TRIG_TIMER && | |
0a85b6f0 MT |
554 | cmd->scan_begin_arg < |
555 | cmd->convert_arg * cmd->scan_end_arg) { | |
086df5b5 | 556 | cmd->scan_begin_arg = |
0a85b6f0 | 557 | cmd->convert_arg * cmd->scan_end_arg; |
086df5b5 IM |
558 | err++; |
559 | } | |
560 | } | |
561 | ||
562 | if (err) | |
563 | return 4; | |
564 | ||
565 | return 0; | |
566 | } | |
567 | #endif | |
568 | ||
569 | /* This function doesn't require a particular form, this is just | |
570 | * what happens to be used in some of the drivers. It should | |
571 | * convert ns nanoseconds to a counter value suitable for programming | |
572 | * the device. Also, it should adjust ns so that it cooresponds to | |
573 | * the actual time that the device will use. */ | |
574 | #if 0 | |
575 | static int cb_pcidda_ns_to_timer(unsigned int *ns, int round) | |
576 | { | |
577 | /* trivial timer */ | |
578 | return *ns; | |
579 | } | |
580 | #endif | |
581 | ||
0a85b6f0 MT |
582 | static int cb_pcidda_ao_winsn(struct comedi_device *dev, |
583 | struct comedi_subdevice *s, | |
584 | struct comedi_insn *insn, unsigned int *data) | |
086df5b5 IM |
585 | { |
586 | unsigned int command; | |
587 | unsigned int channel, range; | |
588 | ||
589 | channel = CR_CHAN(insn->chanspec); | |
590 | range = CR_RANGE(insn->chanspec); | |
591 | ||
2696fb57 | 592 | /* adjust calibration dacs if range has changed */ |
086df5b5 IM |
593 | if (range != devpriv->ao_range[channel]) |
594 | cb_pcidda_calibrate(dev, channel, range); | |
595 | ||
596 | /* output channel configuration */ | |
597 | command = NOSU | ENABLEDAC; | |
598 | ||
599 | /* output channel range */ | |
600 | switch (range) { | |
601 | case 0: | |
602 | command |= BIP | RANGE10V; | |
603 | break; | |
604 | case 1: | |
605 | command |= BIP | RANGE5V; | |
606 | break; | |
607 | case 2: | |
608 | command |= BIP | RANGE2V5; | |
609 | break; | |
610 | case 3: | |
611 | command |= UNIP | RANGE10V; | |
612 | break; | |
613 | case 4: | |
614 | command |= UNIP | RANGE5V; | |
615 | break; | |
616 | case 5: | |
617 | command |= UNIP | RANGE2V5; | |
618 | break; | |
95cd17c9 | 619 | } |
086df5b5 IM |
620 | |
621 | /* output channel specification */ | |
622 | command |= channel << 2; | |
623 | outw(command, devpriv->dac + DACONTROL); | |
624 | ||
625 | /* write data */ | |
626 | outw(data[0], devpriv->dac + DADATA + channel * 2); | |
627 | ||
628 | /* return the number of samples read/written */ | |
629 | return 1; | |
630 | } | |
631 | ||
2696fb57 | 632 | /* lowlevel read from eeprom */ |
da91b269 | 633 | static unsigned int cb_pcidda_serial_in(struct comedi_device *dev) |
086df5b5 IM |
634 | { |
635 | unsigned int value = 0; | |
636 | int i; | |
2696fb57 | 637 | const int value_width = 16; /* number of bits wide values are */ |
086df5b5 IM |
638 | |
639 | for (i = 1; i <= value_width; i++) { | |
2696fb57 | 640 | /* read bits most significant bit first */ |
20db7d7d | 641 | if (inw_p(devpriv->dac + DACALIBRATION1) & SERIAL_OUT_BIT) |
086df5b5 | 642 | value |= 1 << (value_width - i); |
086df5b5 IM |
643 | } |
644 | ||
645 | return value; | |
646 | } | |
647 | ||
2696fb57 | 648 | /* lowlevel write to eeprom/dac */ |
da91b269 | 649 | static void cb_pcidda_serial_out(struct comedi_device *dev, unsigned int value, |
0a85b6f0 | 650 | unsigned int num_bits) |
086df5b5 IM |
651 | { |
652 | int i; | |
653 | ||
654 | for (i = 1; i <= num_bits; i++) { | |
2696fb57 | 655 | /* send bits most significant bit first */ |
086df5b5 IM |
656 | if (value & (1 << (num_bits - i))) |
657 | devpriv->dac_cal1_bits |= SERIAL_IN_BIT; | |
658 | else | |
659 | devpriv->dac_cal1_bits &= ~SERIAL_IN_BIT; | |
660 | outw_p(devpriv->dac_cal1_bits, devpriv->dac + DACALIBRATION1); | |
661 | } | |
662 | } | |
663 | ||
2696fb57 | 664 | /* reads a 16 bit value from board's eeprom */ |
da91b269 | 665 | static unsigned int cb_pcidda_read_eeprom(struct comedi_device *dev, |
0a85b6f0 | 666 | unsigned int address) |
086df5b5 IM |
667 | { |
668 | unsigned int i; | |
669 | unsigned int cal2_bits; | |
670 | unsigned int value; | |
3c5510ba RKM |
671 | /* one caldac for every two dac channels */ |
672 | const int max_num_caldacs = 4; | |
673 | /* bits to send to tell eeprom we want to read */ | |
674 | const int read_instruction = 0x6; | |
086df5b5 IM |
675 | const int instruction_length = 3; |
676 | const int address_length = 8; | |
677 | ||
2696fb57 | 678 | /* send serial output stream to eeprom */ |
086df5b5 | 679 | cal2_bits = SELECT_EEPROM_BIT | DESELECT_REF_DAC_BIT | DUMMY_BIT; |
2696fb57 | 680 | /* deactivate caldacs (one caldac for every two channels) */ |
20db7d7d | 681 | for (i = 0; i < max_num_caldacs; i++) |
086df5b5 | 682 | cal2_bits |= DESELECT_CALDAC_BIT(i); |
086df5b5 IM |
683 | outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); |
684 | ||
2696fb57 | 685 | /* tell eeprom we want to read */ |
086df5b5 | 686 | cb_pcidda_serial_out(dev, read_instruction, instruction_length); |
2696fb57 | 687 | /* send address we want to read from */ |
086df5b5 IM |
688 | cb_pcidda_serial_out(dev, address, address_length); |
689 | ||
690 | value = cb_pcidda_serial_in(dev); | |
691 | ||
2696fb57 | 692 | /* deactivate eeprom */ |
086df5b5 IM |
693 | cal2_bits &= ~SELECT_EEPROM_BIT; |
694 | outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); | |
695 | ||
696 | return value; | |
697 | } | |
698 | ||
2696fb57 | 699 | /* writes to 8 bit calibration dacs */ |
0a85b6f0 MT |
700 | static void cb_pcidda_write_caldac(struct comedi_device *dev, |
701 | unsigned int caldac, unsigned int channel, | |
702 | unsigned int value) | |
086df5b5 IM |
703 | { |
704 | unsigned int cal2_bits; | |
705 | unsigned int i; | |
3c5510ba RKM |
706 | /* caldacs use 3 bit channel specification */ |
707 | const int num_channel_bits = 3; | |
2696fb57 | 708 | const int num_caldac_bits = 8; /* 8 bit calibration dacs */ |
3c5510ba RKM |
709 | /* one caldac for every two dac channels */ |
710 | const int max_num_caldacs = 4; | |
086df5b5 IM |
711 | |
712 | /* write 3 bit channel */ | |
713 | cb_pcidda_serial_out(dev, channel, num_channel_bits); | |
2696fb57 | 714 | /* write 8 bit caldac value */ |
086df5b5 IM |
715 | cb_pcidda_serial_out(dev, value, num_caldac_bits); |
716 | ||
2696fb57 BP |
717 | /* |
718 | * latch stream into appropriate caldac deselect reference dac | |
719 | */ | |
086df5b5 | 720 | cal2_bits = DESELECT_REF_DAC_BIT | DUMMY_BIT; |
2696fb57 | 721 | /* deactivate caldacs (one caldac for every two channels) */ |
20db7d7d | 722 | for (i = 0; i < max_num_caldacs; i++) |
086df5b5 | 723 | cal2_bits |= DESELECT_CALDAC_BIT(i); |
2696fb57 | 724 | /* activate the caldac we want */ |
086df5b5 IM |
725 | cal2_bits &= ~DESELECT_CALDAC_BIT(caldac); |
726 | outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); | |
2696fb57 | 727 | /* deactivate caldac */ |
086df5b5 IM |
728 | cal2_bits |= DESELECT_CALDAC_BIT(caldac); |
729 | outw_p(cal2_bits, devpriv->dac + DACALIBRATION2); | |
730 | } | |
731 | ||
2696fb57 | 732 | /* returns caldac that calibrates given analog out channel */ |
086df5b5 IM |
733 | static unsigned int caldac_number(unsigned int channel) |
734 | { | |
735 | return channel / 2; | |
736 | } | |
737 | ||
2696fb57 | 738 | /* returns caldac channel that provides fine gain for given ao channel */ |
086df5b5 IM |
739 | static unsigned int fine_gain_channel(unsigned int ao_channel) |
740 | { | |
741 | return 4 * (ao_channel % 2); | |
742 | } | |
743 | ||
2696fb57 | 744 | /* returns caldac channel that provides coarse gain for given ao channel */ |
086df5b5 IM |
745 | static unsigned int coarse_gain_channel(unsigned int ao_channel) |
746 | { | |
747 | return 1 + 4 * (ao_channel % 2); | |
748 | } | |
749 | ||
2696fb57 | 750 | /* returns caldac channel that provides coarse offset for given ao channel */ |
086df5b5 IM |
751 | static unsigned int coarse_offset_channel(unsigned int ao_channel) |
752 | { | |
753 | return 2 + 4 * (ao_channel % 2); | |
754 | } | |
755 | ||
2696fb57 | 756 | /* returns caldac channel that provides fine offset for given ao channel */ |
086df5b5 IM |
757 | static unsigned int fine_offset_channel(unsigned int ao_channel) |
758 | { | |
759 | return 3 + 4 * (ao_channel % 2); | |
760 | } | |
761 | ||
2696fb57 | 762 | /* returns eeprom address that provides offset for given ao channel and range */ |
086df5b5 | 763 | static unsigned int offset_eeprom_address(unsigned int ao_channel, |
0a85b6f0 | 764 | unsigned int range) |
086df5b5 IM |
765 | { |
766 | return 0x7 + 2 * range + 12 * ao_channel; | |
767 | } | |
768 | ||
3c5510ba RKM |
769 | /* |
770 | * returns eeprom address that provides gain calibration for given ao | |
771 | * channel and range | |
772 | */ | |
086df5b5 | 773 | static unsigned int gain_eeprom_address(unsigned int ao_channel, |
0a85b6f0 | 774 | unsigned int range) |
086df5b5 IM |
775 | { |
776 | return 0x8 + 2 * range + 12 * ao_channel; | |
777 | } | |
778 | ||
3c5510ba RKM |
779 | /* |
780 | * returns upper byte of eeprom entry, which gives the coarse adjustment | |
781 | * values | |
782 | */ | |
086df5b5 IM |
783 | static unsigned int eeprom_coarse_byte(unsigned int word) |
784 | { | |
785 | return (word >> 8) & 0xff; | |
786 | } | |
787 | ||
2696fb57 | 788 | /* returns lower byte of eeprom entry, which gives the fine adjustment values */ |
086df5b5 IM |
789 | static unsigned int eeprom_fine_byte(unsigned int word) |
790 | { | |
791 | return word & 0xff; | |
792 | } | |
793 | ||
2696fb57 | 794 | /* set caldacs to eeprom values for given channel and range */ |
da91b269 | 795 | static void cb_pcidda_calibrate(struct comedi_device *dev, unsigned int channel, |
0a85b6f0 | 796 | unsigned int range) |
086df5b5 IM |
797 | { |
798 | unsigned int coarse_offset, fine_offset, coarse_gain, fine_gain; | |
799 | ||
3c5510ba | 800 | /* remember range so we can tell when we need to readjust calibration */ |
086df5b5 IM |
801 | devpriv->ao_range[channel] = range; |
802 | ||
2696fb57 | 803 | /* get values from eeprom data */ |
086df5b5 | 804 | coarse_offset = |
0a85b6f0 MT |
805 | eeprom_coarse_byte(devpriv->eeprom_data |
806 | [offset_eeprom_address(channel, range)]); | |
086df5b5 | 807 | fine_offset = |
0a85b6f0 MT |
808 | eeprom_fine_byte(devpriv->eeprom_data |
809 | [offset_eeprom_address(channel, range)]); | |
086df5b5 | 810 | coarse_gain = |
0a85b6f0 MT |
811 | eeprom_coarse_byte(devpriv->eeprom_data |
812 | [gain_eeprom_address(channel, range)]); | |
086df5b5 | 813 | fine_gain = |
0a85b6f0 MT |
814 | eeprom_fine_byte(devpriv->eeprom_data |
815 | [gain_eeprom_address(channel, range)]); | |
086df5b5 | 816 | |
2696fb57 | 817 | /* set caldacs */ |
086df5b5 | 818 | cb_pcidda_write_caldac(dev, caldac_number(channel), |
0a85b6f0 | 819 | coarse_offset_channel(channel), coarse_offset); |
086df5b5 | 820 | cb_pcidda_write_caldac(dev, caldac_number(channel), |
0a85b6f0 | 821 | fine_offset_channel(channel), fine_offset); |
086df5b5 | 822 | cb_pcidda_write_caldac(dev, caldac_number(channel), |
0a85b6f0 | 823 | coarse_gain_channel(channel), coarse_gain); |
086df5b5 | 824 | cb_pcidda_write_caldac(dev, caldac_number(channel), |
0a85b6f0 | 825 | fine_gain_channel(channel), fine_gain); |
086df5b5 IM |
826 | } |
827 | ||
954ca6c9 HS |
828 | static struct comedi_driver cb_pcidda_driver = { |
829 | .driver_name = "cb_pcidda", | |
830 | .module = THIS_MODULE, | |
831 | .attach = cb_pcidda_attach, | |
832 | .detach = cb_pcidda_detach, | |
833 | }; | |
834 | ||
835 | static int __devinit cb_pcidda_pci_probe(struct pci_dev *dev, | |
836 | const struct pci_device_id *ent) | |
727b286b | 837 | { |
954ca6c9 | 838 | return comedi_pci_auto_config(dev, &cb_pcidda_driver); |
727b286b AT |
839 | } |
840 | ||
954ca6c9 | 841 | static void __devexit cb_pcidda_pci_remove(struct pci_dev *dev) |
727b286b AT |
842 | { |
843 | comedi_pci_auto_unconfig(dev); | |
844 | } | |
845 | ||
954ca6c9 HS |
846 | static DEFINE_PCI_DEVICE_TABLE(cb_pcidda_pci_table) = { |
847 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0020) }, | |
848 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0021) }, | |
849 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0022) }, | |
850 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0023) }, | |
851 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0024) }, | |
852 | { PCI_DEVICE(PCI_VENDOR_ID_CB, 0x0025) }, | |
853 | { 0 } | |
727b286b | 854 | }; |
954ca6c9 | 855 | MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table); |
727b286b | 856 | |
954ca6c9 HS |
857 | static struct pci_driver cb_pcidda_pci_driver = { |
858 | .name = "cb_pcidda", | |
859 | .id_table = cb_pcidda_pci_table, | |
860 | .probe = cb_pcidda_pci_probe, | |
861 | .remove = __devexit_p(cb_pcidda_pci_remove), | |
862 | }; | |
863 | module_comedi_pci_driver(cb_pcidda_driver, cb_pcidda_pci_driver); | |
90f703d3 AT |
864 | |
865 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
866 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
867 | MODULE_LICENSE("GPL"); |