Commit | Line | Data |
---|---|---|
0c988d00 EW |
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 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | ||
22 | */ | |
23 | /* | |
24 | Driver: s526 | |
25 | Description: Sensoray 526 driver | |
26 | Devices: [Sensoray] 526 (s526) | |
27 | Author: Richie | |
28 | Everett Wang <everett.wang@everteq.com> | |
29 | Updated: Thu, 14 Sep. 2006 | |
30 | Status: experimental | |
31 | ||
32 | Encoder works | |
33 | Analog input works | |
34 | Analog output works | |
35 | PWM output works | |
36 | Commands are not supported yet. | |
37 | ||
38 | Configuration Options: | |
39 | ||
40 | comedi_config /dev/comedi0 s526 0x2C0,0x3 | |
41 | ||
42 | */ | |
43 | ||
44 | #include "../comedidev.h" | |
45 | #include <linux/ioport.h> | |
46 | ||
47 | #define S526_SIZE 64 | |
48 | ||
49 | #define S526_START_AI_CONV 0 | |
50 | #define S526_AI_READ 0 | |
51 | ||
52 | /* Ports */ | |
53 | #define S526_IOSIZE 0x40 | |
54 | #define S526_NUM_PORTS 27 | |
55 | ||
56 | /* registers */ | |
57 | #define REG_TCR 0x00 | |
58 | #define REG_WDC 0x02 | |
59 | #define REG_DAC 0x04 | |
60 | #define REG_ADC 0x06 | |
61 | #define REG_ADD 0x08 | |
62 | #define REG_DIO 0x0A | |
63 | #define REG_IER 0x0C | |
64 | #define REG_ISR 0x0E | |
65 | #define REG_MSC 0x10 | |
66 | #define REG_C0L 0x12 | |
67 | #define REG_C0H 0x14 | |
68 | #define REG_C0M 0x16 | |
69 | #define REG_C0C 0x18 | |
70 | #define REG_C1L 0x1A | |
71 | #define REG_C1H 0x1C | |
72 | #define REG_C1M 0x1E | |
73 | #define REG_C1C 0x20 | |
74 | #define REG_C2L 0x22 | |
75 | #define REG_C2H 0x24 | |
76 | #define REG_C2M 0x26 | |
77 | #define REG_C2C 0x28 | |
78 | #define REG_C3L 0x2A | |
79 | #define REG_C3H 0x2C | |
80 | #define REG_C3M 0x2E | |
81 | #define REG_C3C 0x30 | |
82 | #define REG_EED 0x32 | |
83 | #define REG_EEC 0x34 | |
84 | ||
85 | static const int s526_ports[] = { | |
86 | REG_TCR, | |
87 | REG_WDC, | |
88 | REG_DAC, | |
89 | REG_ADC, | |
90 | REG_ADD, | |
91 | REG_DIO, | |
92 | REG_IER, | |
93 | REG_ISR, | |
94 | REG_MSC, | |
95 | REG_C0L, | |
96 | REG_C0H, | |
97 | REG_C0M, | |
98 | REG_C0C, | |
99 | REG_C1L, | |
100 | REG_C1H, | |
101 | REG_C1M, | |
102 | REG_C1C, | |
103 | REG_C2L, | |
104 | REG_C2H, | |
105 | REG_C2M, | |
106 | REG_C2C, | |
107 | REG_C3L, | |
108 | REG_C3H, | |
109 | REG_C3M, | |
110 | REG_C3C, | |
111 | REG_EED, | |
112 | REG_EEC | |
113 | }; | |
114 | ||
4b1d53f0 | 115 | struct counter_mode_register_t { |
0c988d00 EW |
116 | unsigned short coutSource:1; |
117 | unsigned short coutPolarity:1; | |
118 | unsigned short autoLoadResetRcap:3; | |
119 | unsigned short hwCtEnableSource:2; | |
120 | unsigned short ctEnableCtrl:2; | |
121 | unsigned short clockSource:2; | |
122 | unsigned short countDir:1; | |
123 | unsigned short countDirCtrl:1; | |
124 | unsigned short outputRegLatchCtrl:1; | |
125 | unsigned short preloadRegSel:1; | |
126 | unsigned short reserved:1; | |
4b1d53f0 | 127 | }; |
0c988d00 EW |
128 | |
129 | union { | |
4b1d53f0 | 130 | struct counter_mode_register_t reg; |
0c988d00 EW |
131 | unsigned short value; |
132 | } cmReg; | |
133 | ||
134 | #define MAX_GPCT_CONFIG_DATA 6 | |
135 | ||
136 | /* Different Application Classes for GPCT Subdevices */ | |
137 | /* The list is not exhaustive and needs discussion! */ | |
dfb0503e | 138 | enum S526_GPCT_APP_CLASS { |
0c988d00 EW |
139 | CountingAndTimeMeasurement, |
140 | SinglePulseGeneration, | |
141 | PulseTrainGeneration, | |
142 | PositionMeasurement, | |
143 | Miscellaneous | |
dfb0503e | 144 | }; |
0c988d00 EW |
145 | |
146 | /* Config struct for different GPCT subdevice Application Classes and | |
147 | their options | |
148 | */ | |
39f76660 | 149 | struct s526GPCTConfig { |
dfb0503e | 150 | enum S526_GPCT_APP_CLASS app; |
0c988d00 | 151 | int data[MAX_GPCT_CONFIG_DATA]; |
39f76660 | 152 | }; |
0c988d00 EW |
153 | |
154 | /* | |
155 | * Board descriptions for two imaginary boards. Describing the | |
156 | * boards in this way is optional, and completely driver-dependent. | |
157 | * Some drivers use arrays such as this, other do not. | |
158 | */ | |
c611ad33 | 159 | struct s526_board { |
0c988d00 EW |
160 | const char *name; |
161 | int gpct_chans; | |
162 | int gpct_bits; | |
163 | int ad_chans; | |
164 | int ad_bits; | |
165 | int da_chans; | |
166 | int da_bits; | |
167 | int have_dio; | |
c611ad33 | 168 | }; |
0c988d00 | 169 | |
c611ad33 | 170 | static const struct s526_board s526_boards[] = { |
0c988d00 EW |
171 | { |
172 | name: "s526", | |
173 | gpct_chans:4, | |
174 | gpct_bits:24, | |
175 | ad_chans:8, | |
176 | ad_bits: 16, | |
177 | da_chans:4, | |
178 | da_bits: 16, | |
179 | have_dio:1, | |
180 | } | |
181 | }; | |
182 | ||
183 | #define ADDR_REG(reg) (dev->iobase + (reg)) | |
184 | #define ADDR_CHAN_REG(reg, chan) (dev->iobase + (reg) + (chan) * 8) | |
185 | ||
186 | /* | |
187 | * Useful for shorthand access to the particular board structure | |
188 | */ | |
c611ad33 | 189 | #define thisboard ((const struct s526_board *)dev->board_ptr) |
0c988d00 EW |
190 | |
191 | /* this structure is for data unique to this hardware driver. If | |
192 | several hardware drivers keep similar information in this structure, | |
71b5f4f1 | 193 | feel free to suggest moving the variable to the struct comedi_device struct. */ |
6dc1ece0 BP |
194 | struct s526_private { |
195 | ||
0c988d00 EW |
196 | int data; |
197 | ||
198 | /* would be useful for a PCI device */ | |
199 | struct pci_dev *pci_dev; | |
200 | ||
201 | /* Used for AO readback */ | |
790c5541 | 202 | unsigned int ao_readback[2]; |
0c988d00 | 203 | |
39f76660 | 204 | struct s526GPCTConfig s526_gpct_config[4]; |
0c988d00 | 205 | unsigned short s526_ai_config; |
6dc1ece0 BP |
206 | }; |
207 | ||
0c988d00 EW |
208 | /* |
209 | * most drivers define the following macro to make it easy to | |
210 | * access the private structure. | |
211 | */ | |
6dc1ece0 | 212 | #define devpriv ((struct s526_private *)dev->private) |
0c988d00 EW |
213 | |
214 | /* | |
139dfbdf | 215 | * The struct comedi_driver structure tells the Comedi core module |
0c988d00 EW |
216 | * which functions to call to configure/deconfigure (attach/detach) |
217 | * the board, and also about the kernel module that contains | |
218 | * the device code. | |
219 | */ | |
0707bb04 | 220 | static int s526_attach(struct comedi_device * dev, struct comedi_devconfig * it); |
71b5f4f1 | 221 | static int s526_detach(struct comedi_device * dev); |
139dfbdf | 222 | static struct comedi_driver driver_s526 = { |
0c988d00 EW |
223 | driver_name:"s526", |
224 | module:THIS_MODULE, | |
225 | attach:s526_attach, | |
226 | detach:s526_detach, | |
227 | /* It is not necessary to implement the following members if you are | |
228 | * writing a driver for a ISA PnP or PCI card */ | |
229 | /* Most drivers will support multiple types of boards by | |
230 | * having an array of board structures. These were defined | |
231 | * in s526_boards[] above. Note that the element 'name' | |
232 | * was first in the structure -- Comedi uses this fact to | |
233 | * extract the name of the board without knowing any details | |
234 | * about the structure except for its length. | |
235 | * When a device is attached (by comedi_config), the name | |
236 | * of the device is given to Comedi, and Comedi tries to | |
237 | * match it by going through the list of board names. If | |
238 | * there is a match, the address of the pointer is put | |
239 | * into dev->board_ptr and driver->attach() is called. | |
240 | * | |
241 | * Note that these are not necessary if you can determine | |
242 | * the type of board in software. ISA PnP, PCI, and PCMCIA | |
243 | * devices are such boards. | |
244 | */ | |
245 | board_name:&s526_boards[0].name, | |
c611ad33 BP |
246 | offset:sizeof(struct s526_board), |
247 | num_names:sizeof(s526_boards) / sizeof(struct s526_board), | |
0c988d00 EW |
248 | }; |
249 | ||
34c43922 | 250 | static int s526_gpct_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 251 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 252 | static int s526_gpct_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 253 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 254 | static int s526_gpct_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 255 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 256 | static int s526_ai_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 257 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 258 | static int s526_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 259 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 260 | static int s526_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 261 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 262 | static int s526_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 263 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 264 | static int s526_dio_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 265 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 266 | static int s526_dio_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 267 | struct comedi_insn * insn, unsigned int * data); |
0c988d00 EW |
268 | |
269 | /* | |
270 | * Attach is called by the Comedi core to configure the driver | |
271 | * for a particular board. If you specified a board_name array | |
272 | * in the driver structure, dev->board_ptr contains that | |
273 | * address. | |
274 | */ | |
0707bb04 | 275 | static int s526_attach(struct comedi_device * dev, struct comedi_devconfig * it) |
0c988d00 | 276 | { |
34c43922 | 277 | struct comedi_subdevice *s; |
0c988d00 EW |
278 | int iobase; |
279 | int i, n; | |
790c5541 | 280 | // short value; |
0c988d00 EW |
281 | // int subdev_channel = 0; |
282 | ||
283 | printk("comedi%d: s526: ", dev->minor); | |
284 | ||
285 | iobase = it->options[0]; | |
286 | if (!iobase || !request_region(iobase, S526_IOSIZE, thisboard->name)) { | |
287 | comedi_error(dev, "I/O port conflict"); | |
288 | return -EIO; | |
289 | } | |
290 | dev->iobase = iobase; | |
291 | ||
292 | printk("iobase=0x%lx\n", dev->iobase); | |
293 | ||
294 | /*** make it a little quieter, exw, 8/29/06 | |
295 | for (i = 0; i < S526_NUM_PORTS; i++) { | |
296 | printk("0x%02x: 0x%04x\n", ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i]))); | |
297 | } | |
298 | ***/ | |
299 | ||
300 | /* | |
301 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
302 | * macro now, since we just initialized it in the last line. | |
303 | */ | |
304 | dev->board_ptr = &s526_boards[0]; | |
305 | ||
306 | dev->board_name = thisboard->name; | |
307 | ||
308 | /* | |
309 | * Allocate the private structure area. alloc_private() is a | |
310 | * convenient macro defined in comedidev.h. | |
311 | */ | |
6dc1ece0 | 312 | if (alloc_private(dev, sizeof(struct s526_private)) < 0) |
0c988d00 EW |
313 | return -ENOMEM; |
314 | ||
315 | /* | |
316 | * Allocate the subdevice structures. alloc_subdevice() is a | |
317 | * convenient macro defined in comedidev.h. | |
318 | */ | |
319 | dev->n_subdevices = 4; | |
320 | if (alloc_subdevices(dev, dev->n_subdevices) < 0) | |
321 | return -ENOMEM; | |
322 | ||
323 | s = dev->subdevices + 0; | |
324 | /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ | |
325 | s->type = COMEDI_SUBD_COUNTER; | |
326 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; | |
327 | /* KG: What does SDF_LSAMPL (see multiq3.c) mean? */ | |
328 | s->n_chan = thisboard->gpct_chans; | |
329 | s->maxdata = 0x00ffffff; /* 24 bit counter */ | |
330 | s->insn_read = s526_gpct_rinsn; | |
331 | s->insn_config = s526_gpct_insn_config; | |
332 | s->insn_write = s526_gpct_winsn; | |
333 | ||
334 | /* Command are not implemented yet, however they are necessary to | |
335 | allocate the necessary memory for the comedi_async struct (used | |
336 | to trigger the GPCT in case of pulsegenerator function */ | |
337 | //s->do_cmd = s526_gpct_cmd; | |
338 | //s->do_cmdtest = s526_gpct_cmdtest; | |
339 | //s->cancel = s526_gpct_cancel; | |
340 | ||
341 | s = dev->subdevices + 1; | |
342 | //dev->read_subdev=s; | |
343 | /* analog input subdevice */ | |
344 | s->type = COMEDI_SUBD_AI; | |
345 | /* we support differential */ | |
346 | s->subdev_flags = SDF_READABLE | SDF_DIFF; | |
347 | /* channels 0 to 7 are the regular differential inputs */ | |
348 | /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ | |
349 | s->n_chan = 10; | |
350 | s->maxdata = 0xffff; | |
351 | s->range_table = &range_bipolar10; | |
352 | s->len_chanlist = 16; /* This is the maximum chanlist length that | |
353 | the board can handle */ | |
354 | s->insn_read = s526_ai_rinsn; | |
355 | s->insn_config = s526_ai_insn_config; | |
356 | ||
357 | s = dev->subdevices + 2; | |
358 | /* analog output subdevice */ | |
359 | s->type = COMEDI_SUBD_AO; | |
360 | s->subdev_flags = SDF_WRITABLE; | |
361 | s->n_chan = 4; | |
362 | s->maxdata = 0xffff; | |
363 | s->range_table = &range_bipolar10; | |
364 | s->insn_write = s526_ao_winsn; | |
365 | s->insn_read = s526_ao_rinsn; | |
366 | ||
367 | s = dev->subdevices + 3; | |
368 | /* digital i/o subdevice */ | |
369 | if (thisboard->have_dio) { | |
370 | s->type = COMEDI_SUBD_DIO; | |
371 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
372 | s->n_chan = 2; | |
373 | s->maxdata = 1; | |
374 | s->range_table = &range_digital; | |
375 | s->insn_bits = s526_dio_insn_bits; | |
376 | s->insn_config = s526_dio_insn_config; | |
377 | } else { | |
378 | s->type = COMEDI_SUBD_UNUSED; | |
379 | } | |
380 | ||
381 | printk("attached\n"); | |
382 | ||
383 | return 1; | |
384 | ||
385 | #if 0 | |
386 | // Example of Counter Application | |
387 | //One-shot (software trigger) | |
388 | cmReg.reg.coutSource = 0; // out RCAP | |
389 | cmReg.reg.coutPolarity = 1; // Polarity inverted | |
390 | cmReg.reg.autoLoadResetRcap = 1; // Auto load 0:disabled, 1:enabled | |
391 | cmReg.reg.hwCtEnableSource = 3; // NOT RCAP | |
392 | cmReg.reg.ctEnableCtrl = 2; // Hardware | |
393 | cmReg.reg.clockSource = 2; // Internal | |
394 | cmReg.reg.countDir = 1; // Down | |
395 | cmReg.reg.countDirCtrl = 1; // Software | |
396 | cmReg.reg.outputRegLatchCtrl = 0; // latch on read | |
397 | cmReg.reg.preloadRegSel = 0; // PR0 | |
398 | cmReg.reg.reserved = 0; | |
399 | ||
400 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
401 | ||
402 | outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel)); | |
403 | outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel)); | |
404 | ||
405 | outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter | |
406 | outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0 | |
407 | ||
408 | outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset RCAP (fires one-shot) | |
409 | ||
410 | #else | |
411 | ||
412 | // Set Counter Mode Register | |
413 | cmReg.reg.coutSource = 0; // out RCAP | |
414 | cmReg.reg.coutPolarity = 0; // Polarity inverted | |
415 | cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled | |
416 | cmReg.reg.hwCtEnableSource = 2; // NOT RCAP | |
417 | cmReg.reg.ctEnableCtrl = 1; // 1: Software, >1 : Hardware | |
418 | cmReg.reg.clockSource = 3; // x4 | |
419 | cmReg.reg.countDir = 0; // up | |
420 | cmReg.reg.countDirCtrl = 0; // quadrature | |
421 | cmReg.reg.outputRegLatchCtrl = 0; // latch on read | |
422 | cmReg.reg.preloadRegSel = 0; // PR0 | |
423 | cmReg.reg.reserved = 0; | |
424 | ||
425 | n = 0; | |
426 | printk("Mode reg=0x%04x, 0x%04lx\n", cmReg.value, ADDR_CHAN_REG(REG_C0M, | |
427 | n)); | |
428 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n)); | |
429 | udelay(1000); | |
430 | printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n))); | |
431 | ||
432 | // Load the pre-laod register high word | |
790c5541 | 433 | // value = (short) (0x55); |
0c988d00 EW |
434 | // outw(value, ADDR_CHAN_REG(REG_C0H, n)); |
435 | ||
436 | // Load the pre-laod register low word | |
790c5541 | 437 | // value = (short)(0xaa55); |
0c988d00 EW |
438 | // outw(value, ADDR_CHAN_REG(REG_C0L, n)); |
439 | ||
440 | // Write the Counter Control Register | |
441 | // outw(value, ADDR_CHAN_REG(REG_C0C, 0)); | |
442 | ||
443 | // Reset the counter if it is software preload | |
444 | if (cmReg.reg.autoLoadResetRcap == 0) { | |
445 | outw(0x8000, ADDR_CHAN_REG(REG_C0C, n)); // Reset the counter | |
446 | outw(0x4000, ADDR_CHAN_REG(REG_C0C, n)); // Load the counter from PR0 | |
447 | } | |
448 | ||
449 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n)); | |
450 | udelay(1000); | |
451 | printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n))); | |
452 | ||
453 | #endif | |
454 | printk("Current registres:\n"); | |
455 | ||
456 | for (i = 0; i < S526_NUM_PORTS; i++) { | |
457 | printk("0x%02lx: 0x%04x\n", ADDR_REG(s526_ports[i]), | |
458 | inw(ADDR_REG(s526_ports[i]))); | |
459 | } | |
460 | return 1; | |
461 | } | |
462 | ||
463 | /* | |
464 | * _detach is called to deconfigure a device. It should deallocate | |
465 | * resources. | |
466 | * This function is also called when _attach() fails, so it should be | |
467 | * careful not to release resources that were not necessarily | |
468 | * allocated by _attach(). dev->private and dev->subdevices are | |
469 | * deallocated automatically by the core. | |
470 | */ | |
71b5f4f1 | 471 | static int s526_detach(struct comedi_device * dev) |
0c988d00 EW |
472 | { |
473 | printk("comedi%d: s526: remove\n", dev->minor); | |
474 | ||
475 | if (dev->iobase > 0) | |
476 | release_region(dev->iobase, S526_IOSIZE); | |
477 | ||
478 | return 0; | |
479 | } | |
480 | ||
34c43922 | 481 | static int s526_gpct_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 482 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
483 | { |
484 | int i; // counts the Data | |
485 | int counter_channel = CR_CHAN(insn->chanspec); | |
486 | unsigned short datalow; | |
487 | unsigned short datahigh; | |
488 | ||
489 | // Check if (n > 0) | |
490 | if (insn->n <= 0) { | |
491 | printk("s526: INSN_READ: n should be > 0\n"); | |
492 | return -EINVAL; | |
493 | } | |
494 | // Read the low word first | |
495 | for (i = 0; i < insn->n; i++) { | |
496 | datalow = inw(ADDR_CHAN_REG(REG_C0L, counter_channel)); | |
497 | datahigh = inw(ADDR_CHAN_REG(REG_C0H, counter_channel)); | |
498 | data[i] = (int)(datahigh & 0x00FF); | |
499 | data[i] = (data[i] << 16) | (datalow & 0xFFFF); | |
500 | // printk("s526 GPCT[%d]: %x(0x%04x, 0x%04x)\n", counter_channel, data[i], datahigh, datalow); | |
501 | } | |
502 | return i; | |
503 | } | |
504 | ||
34c43922 | 505 | static int s526_gpct_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 506 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
507 | { |
508 | int subdev_channel = CR_CHAN(insn->chanspec); // Unpack chanspec | |
509 | int i; | |
790c5541 | 510 | short value; |
0c988d00 EW |
511 | |
512 | // printk("s526: GPCT_INSN_CONFIG: Configuring Channel %d\n", subdev_channel); | |
513 | ||
514 | for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) { | |
515 | devpriv->s526_gpct_config[subdev_channel].data[i] = | |
516 | insn->data[i]; | |
517 | // printk("data[%d]=%x\n", i, insn->data[i]); | |
518 | } | |
519 | ||
520 | // Check what type of Counter the user requested, data[0] contains | |
521 | // the Application type | |
522 | switch (insn->data[0]) { | |
523 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: | |
524 | /* | |
525 | data[0]: Application Type | |
526 | data[1]: Counter Mode Register Value | |
527 | data[2]: Pre-load Register Value | |
528 | data[3]: Conter Control Register | |
529 | */ | |
530 | printk("s526: GPCT_INSN_CONFIG: Configuring Encoder\n"); | |
531 | devpriv->s526_gpct_config[subdev_channel].app = | |
532 | PositionMeasurement; | |
533 | ||
534 | /* | |
535 | // Example of Counter Application | |
536 | //One-shot (software trigger) | |
537 | cmReg.reg.coutSource = 0; // out RCAP | |
538 | cmReg.reg.coutPolarity = 1; // Polarity inverted | |
539 | cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled | |
540 | cmReg.reg.hwCtEnableSource = 3; // NOT RCAP | |
541 | cmReg.reg.ctEnableCtrl = 2; // Hardware | |
542 | cmReg.reg.clockSource = 2; // Internal | |
543 | cmReg.reg.countDir = 1; // Down | |
544 | cmReg.reg.countDirCtrl = 1; // Software | |
545 | cmReg.reg.outputRegLatchCtrl = 0; // latch on read | |
546 | cmReg.reg.preloadRegSel = 0; // PR0 | |
547 | cmReg.reg.reserved = 0; | |
548 | ||
549 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
550 | ||
551 | outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel)); | |
552 | outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel)); | |
553 | ||
554 | outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter | |
555 | outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0 | |
556 | ||
557 | outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset RCAP (fires one-shot) | |
558 | ||
559 | */ | |
560 | ||
561 | #if 1 | |
562 | // Set Counter Mode Register | |
563 | cmReg.reg.coutSource = 0; // out RCAP | |
564 | cmReg.reg.coutPolarity = 0; // Polarity inverted | |
565 | cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled | |
566 | cmReg.reg.hwCtEnableSource = 2; // NOT RCAP | |
567 | cmReg.reg.ctEnableCtrl = 1; // 1: Software, >1 : Hardware | |
568 | cmReg.reg.clockSource = 3; // x4 | |
569 | cmReg.reg.countDir = 0; // up | |
570 | cmReg.reg.countDirCtrl = 0; // quadrature | |
571 | cmReg.reg.outputRegLatchCtrl = 0; // latch on read | |
572 | cmReg.reg.preloadRegSel = 0; // PR0 | |
573 | cmReg.reg.reserved = 0; | |
574 | ||
575 | // Set Counter Mode Register | |
576 | // printk("s526: Counter Mode register=%x\n", cmReg.value); | |
577 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
578 | ||
579 | // Reset the counter if it is software preload | |
580 | if (cmReg.reg.autoLoadResetRcap == 0) { | |
581 | outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter | |
582 | // outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0 | |
583 | } | |
584 | #else | |
585 | cmReg.reg.countDirCtrl = 0; // 0 quadrature, 1 software control | |
586 | ||
587 | // data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 | |
588 | if (insn->data[1] == GPCT_X2) { | |
589 | cmReg.reg.clockSource = 1; | |
590 | } else if (insn->data[1] == GPCT_X4) { | |
591 | cmReg.reg.clockSource = 2; | |
592 | } else { | |
593 | cmReg.reg.clockSource = 0; | |
594 | } | |
595 | ||
596 | // When to take into account the indexpulse: | |
597 | if (insn->data[2] == GPCT_IndexPhaseLowLow) { | |
598 | } else if (insn->data[2] == GPCT_IndexPhaseLowHigh) { | |
599 | } else if (insn->data[2] == GPCT_IndexPhaseHighLow) { | |
600 | } else if (insn->data[2] == GPCT_IndexPhaseHighHigh) { | |
601 | } | |
602 | // Take into account the index pulse? | |
603 | if (insn->data[3] == GPCT_RESET_COUNTER_ON_INDEX) | |
604 | cmReg.reg.autoLoadResetRcap = 4; // Auto load with INDEX^ | |
605 | ||
606 | // Set Counter Mode Register | |
790c5541 | 607 | cmReg.value = (short) (insn->data[1] & 0xFFFF); |
0c988d00 EW |
608 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); |
609 | ||
610 | // Load the pre-laod register high word | |
790c5541 | 611 | value = (short) ((insn->data[2] >> 16) & 0xFFFF); |
0c988d00 EW |
612 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
613 | ||
614 | // Load the pre-laod register low word | |
790c5541 | 615 | value = (short) (insn->data[2] & 0xFFFF); |
0c988d00 EW |
616 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
617 | ||
618 | // Write the Counter Control Register | |
619 | if (insn->data[3] != 0) { | |
790c5541 | 620 | value = (short) (insn->data[3] & 0xFFFF); |
0c988d00 EW |
621 | outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); |
622 | } | |
623 | // Reset the counter if it is software preload | |
624 | if (cmReg.reg.autoLoadResetRcap == 0) { | |
625 | outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter | |
626 | outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0 | |
627 | } | |
628 | #endif | |
629 | break; | |
630 | ||
631 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
632 | /* | |
633 | data[0]: Application Type | |
634 | data[1]: Counter Mode Register Value | |
635 | data[2]: Pre-load Register 0 Value | |
636 | data[3]: Pre-load Register 1 Value | |
637 | data[4]: Conter Control Register | |
638 | */ | |
639 | printk("s526: GPCT_INSN_CONFIG: Configuring SPG\n"); | |
640 | devpriv->s526_gpct_config[subdev_channel].app = | |
641 | SinglePulseGeneration; | |
642 | ||
643 | // Set Counter Mode Register | |
790c5541 | 644 | cmReg.value = (short) (insn->data[1] & 0xFFFF); |
0c988d00 EW |
645 | cmReg.reg.preloadRegSel = 0; // PR0 |
646 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
647 | ||
648 | // Load the pre-laod register 0 high word | |
790c5541 | 649 | value = (short) ((insn->data[2] >> 16) & 0xFFFF); |
0c988d00 EW |
650 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
651 | ||
652 | // Load the pre-laod register 0 low word | |
790c5541 | 653 | value = (short) (insn->data[2] & 0xFFFF); |
0c988d00 EW |
654 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
655 | ||
656 | // Set Counter Mode Register | |
790c5541 | 657 | cmReg.value = (short) (insn->data[1] & 0xFFFF); |
0c988d00 EW |
658 | cmReg.reg.preloadRegSel = 1; // PR1 |
659 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
660 | ||
661 | // Load the pre-laod register 1 high word | |
790c5541 | 662 | value = (short) ((insn->data[3] >> 16) & 0xFFFF); |
0c988d00 EW |
663 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
664 | ||
665 | // Load the pre-laod register 1 low word | |
790c5541 | 666 | value = (short) (insn->data[3] & 0xFFFF); |
0c988d00 EW |
667 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
668 | ||
669 | // Write the Counter Control Register | |
670 | if (insn->data[3] != 0) { | |
790c5541 | 671 | value = (short) (insn->data[3] & 0xFFFF); |
0c988d00 EW |
672 | outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); |
673 | } | |
674 | break; | |
675 | ||
676 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
677 | /* | |
678 | data[0]: Application Type | |
679 | data[1]: Counter Mode Register Value | |
680 | data[2]: Pre-load Register 0 Value | |
681 | data[3]: Pre-load Register 1 Value | |
682 | data[4]: Conter Control Register | |
683 | */ | |
684 | printk("s526: GPCT_INSN_CONFIG: Configuring PTG\n"); | |
685 | devpriv->s526_gpct_config[subdev_channel].app = | |
686 | PulseTrainGeneration; | |
687 | ||
688 | // Set Counter Mode Register | |
790c5541 | 689 | cmReg.value = (short) (insn->data[1] & 0xFFFF); |
0c988d00 EW |
690 | cmReg.reg.preloadRegSel = 0; // PR0 |
691 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
692 | ||
693 | // Load the pre-laod register 0 high word | |
790c5541 | 694 | value = (short) ((insn->data[2] >> 16) & 0xFFFF); |
0c988d00 EW |
695 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
696 | ||
697 | // Load the pre-laod register 0 low word | |
790c5541 | 698 | value = (short) (insn->data[2] & 0xFFFF); |
0c988d00 EW |
699 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
700 | ||
701 | // Set Counter Mode Register | |
790c5541 | 702 | cmReg.value = (short) (insn->data[1] & 0xFFFF); |
0c988d00 EW |
703 | cmReg.reg.preloadRegSel = 1; // PR1 |
704 | outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
705 | ||
706 | // Load the pre-laod register 1 high word | |
790c5541 | 707 | value = (short) ((insn->data[3] >> 16) & 0xFFFF); |
0c988d00 EW |
708 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
709 | ||
710 | // Load the pre-laod register 1 low word | |
790c5541 | 711 | value = (short) (insn->data[3] & 0xFFFF); |
0c988d00 EW |
712 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
713 | ||
714 | // Write the Counter Control Register | |
715 | if (insn->data[3] != 0) { | |
790c5541 | 716 | value = (short) (insn->data[3] & 0xFFFF); |
0c988d00 EW |
717 | outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); |
718 | } | |
719 | break; | |
720 | ||
721 | default: | |
722 | printk("s526: unsupported GPCT_insn_config\n"); | |
723 | return -EINVAL; | |
724 | break; | |
725 | } | |
726 | ||
727 | return insn->n; | |
728 | } | |
729 | ||
34c43922 | 730 | static int s526_gpct_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 731 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
732 | { |
733 | int subdev_channel = CR_CHAN(insn->chanspec); // Unpack chanspec | |
790c5541 | 734 | short value; |
0c988d00 EW |
735 | |
736 | printk("s526: GPCT_INSN_WRITE on channel %d\n", subdev_channel); | |
737 | cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel)); | |
738 | printk("s526: Counter Mode Register: %x\n", cmReg.value); | |
739 | // Check what Application of Counter this channel is configured for | |
740 | switch (devpriv->s526_gpct_config[subdev_channel].app) { | |
741 | case PositionMeasurement: | |
742 | printk("S526: INSN_WRITE: PM\n"); | |
743 | outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, | |
744 | subdev_channel)); | |
745 | outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); | |
746 | break; | |
747 | ||
748 | case SinglePulseGeneration: | |
749 | printk("S526: INSN_WRITE: SPG\n"); | |
750 | outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, | |
751 | subdev_channel)); | |
752 | outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); | |
753 | break; | |
754 | ||
755 | case PulseTrainGeneration: | |
756 | /* data[0] contains the PULSE_WIDTH | |
757 | data[1] contains the PULSE_PERIOD | |
758 | @pre PULSE_PERIOD > PULSE_WIDTH > 0 | |
759 | The above periods must be expressed as a multiple of the | |
760 | pulse frequency on the selected source | |
761 | */ | |
762 | printk("S526: INSN_WRITE: PTG\n"); | |
763 | if ((insn->data[1] > insn->data[0]) && (insn->data[0] > 0)) { | |
764 | (devpriv->s526_gpct_config[subdev_channel]).data[0] = | |
765 | insn->data[0]; | |
766 | (devpriv->s526_gpct_config[subdev_channel]).data[1] = | |
767 | insn->data[1]; | |
768 | } else { | |
769 | printk("%d \t %d\n", insn->data[1], insn->data[2]); | |
770 | printk("s526: INSN_WRITE: PTG: Problem with Pulse params\n"); | |
771 | return -EINVAL; | |
772 | } | |
773 | ||
790c5541 | 774 | value = (short) ((*data >> 16) & 0xFFFF); |
0c988d00 | 775 | outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); |
790c5541 | 776 | value = (short) (*data & 0xFFFF); |
0c988d00 EW |
777 | outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); |
778 | break; | |
779 | default: // Impossible | |
780 | printk("s526: INSN_WRITE: Functionality %d not implemented yet\n", devpriv->s526_gpct_config[subdev_channel].app); | |
781 | return -EINVAL; | |
782 | break; | |
783 | } | |
784 | // return the number of samples written | |
785 | return insn->n; | |
786 | } | |
787 | ||
788 | #define ISR_ADC_DONE 0x4 | |
34c43922 | 789 | static int s526_ai_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 790 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
791 | { |
792 | int result = -EINVAL; | |
793 | ||
794 | if (insn->n < 1) | |
795 | return result; | |
796 | ||
797 | result = insn->n; | |
798 | ||
799 | /* data[0] : channels was set in relevant bits. | |
800 | data[1] : delay | |
801 | */ | |
802 | /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to | |
803 | * enable channels here. The channel should be enabled in the | |
804 | * INSN_READ handler. */ | |
805 | ||
806 | // Enable ADC interrupt | |
807 | outw(ISR_ADC_DONE, ADDR_REG(REG_IER)); | |
808 | // printk("s526: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC))); | |
809 | devpriv->s526_ai_config = (data[0] & 0x3FF) << 5; | |
810 | if (data[1] > 0) | |
811 | devpriv->s526_ai_config |= 0x8000; //set the delay | |
812 | ||
813 | devpriv->s526_ai_config |= 0x0001; // ADC start bit. | |
814 | ||
815 | return result; | |
816 | } | |
817 | ||
818 | /* | |
819 | * "instructions" read/write data in "one-shot" or "software-triggered" | |
820 | * mode. | |
821 | */ | |
34c43922 | 822 | static int s526_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 823 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
824 | { |
825 | int n, i; | |
826 | int chan = CR_CHAN(insn->chanspec); | |
827 | unsigned short value; | |
828 | unsigned int d; | |
829 | unsigned int status; | |
830 | ||
831 | /* Set configured delay, enable channel for this channel only, | |
832 | * select "ADC read" channel, set "ADC start" bit. */ | |
833 | value = (devpriv->s526_ai_config & 0x8000) | | |
834 | ((1 << 5) << chan) | (chan << 1) | 0x0001; | |
835 | ||
836 | /* convert n samples */ | |
837 | for (n = 0; n < insn->n; n++) { | |
838 | /* trigger conversion */ | |
839 | outw(value, ADDR_REG(REG_ADC)); | |
840 | // printk("s526: Wrote 0x%04x to ADC\n", value); | |
841 | // printk("s526: ADC reg=0x%04x\n", inw(ADDR_REG(REG_ADC))); | |
842 | ||
843 | #define TIMEOUT 100 | |
844 | /* wait for conversion to end */ | |
845 | for (i = 0; i < TIMEOUT; i++) { | |
846 | status = inw(ADDR_REG(REG_ISR)); | |
847 | if (status & ISR_ADC_DONE) { | |
848 | outw(ISR_ADC_DONE, ADDR_REG(REG_ISR)); | |
849 | break; | |
850 | } | |
851 | } | |
852 | if (i == TIMEOUT) { | |
853 | /* rt_printk() should be used instead of printk() | |
854 | * whenever the code can be called from real-time. */ | |
855 | rt_printk("s526: ADC(0x%04x) timeout\n", | |
856 | inw(ADDR_REG(REG_ISR))); | |
857 | return -ETIMEDOUT; | |
858 | } | |
859 | ||
860 | /* read data */ | |
861 | d = inw(ADDR_REG(REG_ADD)); | |
862 | // printk("AI[%d]=0x%04x\n", n, (unsigned short)(d & 0xFFFF)); | |
863 | ||
864 | /* munge data */ | |
865 | data[n] = d ^ 0x8000; | |
866 | } | |
867 | ||
868 | /* return the number of samples read/written */ | |
869 | return n; | |
870 | } | |
871 | ||
34c43922 | 872 | static int s526_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 873 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
874 | { |
875 | int i; | |
876 | int chan = CR_CHAN(insn->chanspec); | |
877 | unsigned short val; | |
878 | ||
879 | // printk("s526_ao_winsn\n"); | |
880 | val = chan << 1; | |
881 | // outw(val, dev->iobase + REG_DAC); | |
882 | outw(val, ADDR_REG(REG_DAC)); | |
883 | ||
884 | /* Writing a list of values to an AO channel is probably not | |
885 | * very useful, but that's how the interface is defined. */ | |
886 | for (i = 0; i < insn->n; i++) { | |
887 | /* a typical programming sequence */ | |
888 | // outw(data[i], dev->iobase + REG_ADD); // write the data to preload register | |
889 | outw(data[i], ADDR_REG(REG_ADD)); // write the data to preload register | |
890 | devpriv->ao_readback[chan] = data[i]; | |
891 | // outw(val + 1, dev->iobase + REG_DAC); // starts the D/A conversion. | |
892 | outw(val + 1, ADDR_REG(REG_DAC)); // starts the D/A conversion. | |
893 | } | |
894 | ||
895 | /* return the number of samples read/written */ | |
896 | return i; | |
897 | } | |
898 | ||
899 | /* AO subdevices should have a read insn as well as a write insn. | |
900 | * Usually this means copying a value stored in devpriv. */ | |
34c43922 | 901 | static int s526_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 902 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
903 | { |
904 | int i; | |
905 | int chan = CR_CHAN(insn->chanspec); | |
906 | ||
907 | for (i = 0; i < insn->n; i++) | |
908 | data[i] = devpriv->ao_readback[chan]; | |
909 | ||
910 | return i; | |
911 | } | |
912 | ||
913 | /* DIO devices are slightly special. Although it is possible to | |
914 | * implement the insn_read/insn_write interface, it is much more | |
915 | * useful to applications if you implement the insn_bits interface. | |
916 | * This allows packed reading/writing of the DIO channels. The | |
917 | * comedi core can convert between insn_bits and insn_read/write */ | |
34c43922 | 918 | static int s526_dio_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 919 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
920 | { |
921 | if (insn->n != 2) | |
922 | return -EINVAL; | |
923 | ||
924 | /* The insn data is a mask in data[0] and the new data | |
925 | * in data[1], each channel cooresponding to a bit. */ | |
926 | if (data[0]) { | |
927 | s->state &= ~data[0]; | |
928 | s->state |= data[0] & data[1]; | |
929 | /* Write out the new digital output lines */ | |
930 | outw(s->state, ADDR_REG(REG_DIO)); | |
931 | } | |
932 | ||
933 | /* on return, data[1] contains the value of the digital | |
934 | * input and output lines. */ | |
935 | data[1] = inw(ADDR_REG(REG_DIO)) & 0xFF; // low 8 bits are the data | |
936 | /* or we could just return the software copy of the output values if | |
937 | * it was a purely digital output subdevice */ | |
938 | //data[1]=s->state; | |
939 | ||
940 | return 2; | |
941 | } | |
942 | ||
34c43922 | 943 | static int s526_dio_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 944 | struct comedi_insn * insn, unsigned int * data) |
0c988d00 EW |
945 | { |
946 | int chan = CR_CHAN(insn->chanspec); | |
790c5541 | 947 | short value; |
0c988d00 EW |
948 | |
949 | printk("S526 DIO insn_config\n"); | |
950 | ||
951 | if (insn->n != 1) | |
952 | return -EINVAL; | |
953 | ||
954 | value = inw(ADDR_REG(REG_DIO)); | |
955 | ||
956 | /* The input or output configuration of each digital line is | |
957 | * configured by a special insn_config instruction. chanspec | |
958 | * contains the channel to be changed, and data[0] contains the | |
959 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
960 | ||
961 | if (data[0] == COMEDI_OUTPUT) { | |
962 | value |= 1 << (chan + 10); // bit 10/11 set the group 1/2's mode | |
963 | s->io_bits |= (0xF << chan); | |
964 | } else { | |
965 | value &= ~(1 << (chan + 10)); // 1 is output, 0 is input. | |
966 | s->io_bits &= ~(0xF << chan); | |
967 | } | |
968 | outw(value, ADDR_REG(REG_DIO)); | |
969 | ||
970 | return 1; | |
971 | } | |
972 | ||
973 | /* | |
974 | * A convenient macro that defines init_module() and cleanup_module(), | |
975 | * as necessary. | |
976 | */ | |
977 | COMEDI_INITCLEANUP(driver_s526); |