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