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. | |
0c988d00 EW |
17 | */ |
18 | /* | |
19 | Driver: s526 | |
20 | Description: Sensoray 526 driver | |
21 | Devices: [Sensoray] 526 (s526) | |
22 | Author: Richie | |
23 | Everett Wang <everett.wang@everteq.com> | |
24 | Updated: Thu, 14 Sep. 2006 | |
25 | Status: experimental | |
26 | ||
27 | Encoder works | |
28 | Analog input works | |
29 | Analog output works | |
30 | PWM output works | |
31 | Commands are not supported yet. | |
32 | ||
33 | Configuration Options: | |
34 | ||
35 | comedi_config /dev/comedi0 s526 0x2C0,0x3 | |
36 | ||
37 | */ | |
38 | ||
ce157f80 | 39 | #include <linux/module.h> |
0c988d00 | 40 | #include "../comedidev.h" |
2b0318a6 | 41 | #include <asm/byteorder.h> |
0c988d00 | 42 | |
0c988d00 EW |
43 | #define S526_START_AI_CONV 0 |
44 | #define S526_AI_READ 0 | |
45 | ||
46 | /* Ports */ | |
0c988d00 EW |
47 | #define S526_NUM_PORTS 27 |
48 | ||
49 | /* registers */ | |
50 | #define REG_TCR 0x00 | |
51 | #define REG_WDC 0x02 | |
52 | #define REG_DAC 0x04 | |
53 | #define REG_ADC 0x06 | |
54 | #define REG_ADD 0x08 | |
55 | #define REG_DIO 0x0A | |
56 | #define REG_IER 0x0C | |
57 | #define REG_ISR 0x0E | |
58 | #define REG_MSC 0x10 | |
59 | #define REG_C0L 0x12 | |
60 | #define REG_C0H 0x14 | |
61 | #define REG_C0M 0x16 | |
62 | #define REG_C0C 0x18 | |
63 | #define REG_C1L 0x1A | |
64 | #define REG_C1H 0x1C | |
65 | #define REG_C1M 0x1E | |
66 | #define REG_C1C 0x20 | |
67 | #define REG_C2L 0x22 | |
68 | #define REG_C2H 0x24 | |
69 | #define REG_C2M 0x26 | |
70 | #define REG_C2C 0x28 | |
71 | #define REG_C3L 0x2A | |
72 | #define REG_C3H 0x2C | |
73 | #define REG_C3M 0x2E | |
74 | #define REG_C3C 0x30 | |
75 | #define REG_EED 0x32 | |
76 | #define REG_EEC 0x34 | |
77 | ||
4b1d53f0 | 78 | struct counter_mode_register_t { |
c9c62f4e | 79 | #if defined(__LITTLE_ENDIAN_BITFIELD) |
0c988d00 EW |
80 | unsigned short coutSource:1; |
81 | unsigned short coutPolarity:1; | |
82 | unsigned short autoLoadResetRcap:3; | |
83 | unsigned short hwCtEnableSource:2; | |
84 | unsigned short ctEnableCtrl:2; | |
85 | unsigned short clockSource:2; | |
86 | unsigned short countDir:1; | |
87 | unsigned short countDirCtrl:1; | |
88 | unsigned short outputRegLatchCtrl:1; | |
89 | unsigned short preloadRegSel:1; | |
90 | unsigned short reserved:1; | |
2b0318a6 IA |
91 | #elif defined(__BIG_ENDIAN_BITFIELD) |
92 | unsigned short reserved:1; | |
93 | unsigned short preloadRegSel:1; | |
94 | unsigned short outputRegLatchCtrl:1; | |
95 | unsigned short countDirCtrl:1; | |
96 | unsigned short countDir:1; | |
97 | unsigned short clockSource:2; | |
98 | unsigned short ctEnableCtrl:2; | |
99 | unsigned short hwCtEnableSource:2; | |
100 | unsigned short autoLoadResetRcap:3; | |
101 | unsigned short coutPolarity:1; | |
102 | unsigned short coutSource:1; | |
103 | #else | |
104 | #error Unknown bit field order | |
105 | #endif | |
4b1d53f0 | 106 | }; |
0c988d00 | 107 | |
ca98ee7b | 108 | union cmReg { |
4b1d53f0 | 109 | struct counter_mode_register_t reg; |
0c988d00 | 110 | unsigned short value; |
ca98ee7b | 111 | }; |
0c988d00 | 112 | |
6dc1ece0 | 113 | struct s526_private { |
675f98f1 | 114 | unsigned int gpct_config[4]; |
bda60cbf | 115 | unsigned short ai_config; |
6dc1ece0 BP |
116 | }; |
117 | ||
0a85b6f0 | 118 | static int s526_gpct_rinsn(struct comedi_device *dev, |
2a29edf6 HS |
119 | struct comedi_subdevice *s, |
120 | struct comedi_insn *insn, | |
0a85b6f0 | 121 | unsigned int *data) |
0c988d00 | 122 | { |
43a35276 | 123 | unsigned int chan = CR_CHAN(insn->chanspec); |
2a29edf6 HS |
124 | unsigned long chan_iobase = dev->iobase + chan * 8; |
125 | unsigned int lo; | |
126 | unsigned int hi; | |
43a35276 | 127 | int i; |
0c988d00 | 128 | |
0c988d00 | 129 | for (i = 0; i < insn->n; i++) { |
2a29edf6 HS |
130 | /* Read the low word first */ |
131 | lo = inw(chan_iobase + REG_C0L) & 0xffff; | |
132 | hi = inw(chan_iobase + REG_C0H) & 0xff; | |
133 | ||
134 | data[i] = (hi << 16) | lo; | |
0c988d00 | 135 | } |
2a29edf6 HS |
136 | |
137 | return insn->n; | |
0c988d00 EW |
138 | } |
139 | ||
0a85b6f0 MT |
140 | static int s526_gpct_insn_config(struct comedi_device *dev, |
141 | struct comedi_subdevice *s, | |
5a5614cb HS |
142 | struct comedi_insn *insn, |
143 | unsigned int *data) | |
0c988d00 | 144 | { |
5f221062 | 145 | struct s526_private *devpriv = dev->private; |
43a35276 | 146 | unsigned int chan = CR_CHAN(insn->chanspec); |
5a5614cb HS |
147 | unsigned long chan_iobase = dev->iobase + chan * 8; |
148 | unsigned int val; | |
ca98ee7b | 149 | union cmReg cmReg; |
0c988d00 | 150 | |
232f6502 BP |
151 | /* Check what type of Counter the user requested, data[0] contains */ |
152 | /* the Application type */ | |
b2abd982 | 153 | switch (data[0]) { |
0c988d00 EW |
154 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
155 | /* | |
156 | data[0]: Application Type | |
157 | data[1]: Counter Mode Register Value | |
158 | data[2]: Pre-load Register Value | |
159 | data[3]: Conter Control Register | |
160 | */ | |
675f98f1 | 161 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 162 | |
232f6502 | 163 | #if 0 |
0a85b6f0 MT |
164 | /* Example of Counter Application */ |
165 | /* One-shot (software trigger) */ | |
166 | cmReg.reg.coutSource = 0; /* out RCAP */ | |
167 | cmReg.reg.coutPolarity = 1; /* Polarity inverted */ | |
c9c62f4e | 168 | cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */ |
0a85b6f0 MT |
169 | cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ |
170 | cmReg.reg.ctEnableCtrl = 2; /* Hardware */ | |
171 | cmReg.reg.clockSource = 2; /* Internal */ | |
172 | cmReg.reg.countDir = 1; /* Down */ | |
173 | cmReg.reg.countDirCtrl = 1; /* Software */ | |
174 | cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ | |
175 | cmReg.reg.preloadRegSel = 0; /* PR0 */ | |
176 | cmReg.reg.reserved = 0; | |
0c988d00 | 177 | |
5a5614cb | 178 | outw(cmReg.value, chan_iobase + REG_C0M); |
0a85b6f0 | 179 | |
5a5614cb HS |
180 | outw(0x0001, chan_iobase + REG_C0H); |
181 | outw(0x3C68, chan_iobase + REG_C0L); | |
0a85b6f0 | 182 | |
c9c62f4e | 183 | /* Reset the counter */ |
5a5614cb | 184 | outw(0x8000, chan_iobase + REG_C0C); |
c9c62f4e | 185 | /* Load the counter from PR0 */ |
5a5614cb | 186 | outw(0x4000, chan_iobase + REG_C0C); |
0c988d00 | 187 | |
c9c62f4e | 188 | /* Reset RCAP (fires one-shot) */ |
5a5614cb | 189 | outw(0x0008, chan_iobase + REG_C0C); |
0c988d00 | 190 | |
232f6502 | 191 | #endif |
0c988d00 EW |
192 | |
193 | #if 1 | |
232f6502 | 194 | /* Set Counter Mode Register */ |
5a5614cb HS |
195 | cmReg.value = data[1] & 0xffff; |
196 | outw(cmReg.value, chan_iobase + REG_C0M); | |
0c988d00 | 197 | |
232f6502 | 198 | /* Reset the counter if it is software preload */ |
0c988d00 | 199 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 200 | /* Reset the counter */ |
5a5614cb | 201 | outw(0x8000, chan_iobase + REG_C0C); |
c9c62f4e | 202 | /* Load the counter from PR0 |
5a5614cb | 203 | * outw(0x4000, chan_iobase + REG_C0C); |
c9c62f4e | 204 | */ |
0c988d00 EW |
205 | } |
206 | #else | |
c9c62f4e XF |
207 | /* 0 quadrature, 1 software control */ |
208 | cmReg.reg.countDirCtrl = 0; | |
0c988d00 | 209 | |
232f6502 | 210 | /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ |
b2abd982 | 211 | if (data[1] == GPCT_X2) |
0c988d00 | 212 | cmReg.reg.clockSource = 1; |
b2abd982 | 213 | else if (data[1] == GPCT_X4) |
0c988d00 | 214 | cmReg.reg.clockSource = 2; |
c9c62f4e | 215 | else |
0c988d00 | 216 | cmReg.reg.clockSource = 0; |
0c988d00 | 217 | |
232f6502 | 218 | /* When to take into account the indexpulse: */ |
b2abd982 HS |
219 | /*if (data[2] == GPCT_IndexPhaseLowLow) { |
220 | } else if (data[2] == GPCT_IndexPhaseLowHigh) { | |
221 | } else if (data[2] == GPCT_IndexPhaseHighLow) { | |
222 | } else if (data[2] == GPCT_IndexPhaseHighHigh) { | |
c9c62f4e | 223 | }*/ |
232f6502 | 224 | /* Take into account the index pulse? */ |
b2abd982 | 225 | if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) |
c9c62f4e XF |
226 | /* Auto load with INDEX^ */ |
227 | cmReg.reg.autoLoadResetRcap = 4; | |
0c988d00 | 228 | |
232f6502 | 229 | /* Set Counter Mode Register */ |
5a5614cb HS |
230 | cmReg.value = data[1] & 0xffff; |
231 | outw(cmReg.value, chan_iobase + REG_C0M); | |
0c988d00 | 232 | |
5044a2c0 | 233 | /* Load the pre-load register high word */ |
5a5614cb HS |
234 | val = (data[2] >> 16) & 0xffff; |
235 | outw(val, chan_iobase + REG_C0H); | |
0c988d00 | 236 | |
5044a2c0 | 237 | /* Load the pre-load register low word */ |
5a5614cb HS |
238 | val = data[2] & 0xffff; |
239 | outw(val, chan_iobase + REG_C0L); | |
0c988d00 | 240 | |
232f6502 | 241 | /* Write the Counter Control Register */ |
5a5614cb HS |
242 | if (data[3]) { |
243 | val = data[3] & 0xffff; | |
244 | outw(val, chan_iobase + REG_C0C); | |
0c988d00 | 245 | } |
232f6502 | 246 | /* Reset the counter if it is software preload */ |
0c988d00 | 247 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 248 | /* Reset the counter */ |
5a5614cb | 249 | outw(0x8000, chan_iobase + REG_C0C); |
c9c62f4e | 250 | /* Load the counter from PR0 */ |
5a5614cb | 251 | outw(0x4000, chan_iobase + REG_C0C); |
0c988d00 EW |
252 | } |
253 | #endif | |
254 | break; | |
255 | ||
256 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
257 | /* | |
258 | data[0]: Application Type | |
259 | data[1]: Counter Mode Register Value | |
260 | data[2]: Pre-load Register 0 Value | |
261 | data[3]: Pre-load Register 1 Value | |
262 | data[4]: Conter Control Register | |
263 | */ | |
675f98f1 | 264 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 265 | |
232f6502 | 266 | /* Set Counter Mode Register */ |
5a5614cb | 267 | cmReg.value = data[1] & 0xffff; |
232f6502 | 268 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
5a5614cb | 269 | outw(cmReg.value, chan_iobase + REG_C0M); |
0c988d00 | 270 | |
5044a2c0 | 271 | /* Load the pre-load register 0 high word */ |
5a5614cb HS |
272 | val = (data[2] >> 16) & 0xffff; |
273 | outw(val, chan_iobase + REG_C0H); | |
0c988d00 | 274 | |
5044a2c0 | 275 | /* Load the pre-load register 0 low word */ |
5a5614cb HS |
276 | val = data[2] & 0xffff; |
277 | outw(val, chan_iobase + REG_C0L); | |
0c988d00 | 278 | |
232f6502 | 279 | /* Set Counter Mode Register */ |
5a5614cb | 280 | cmReg.value = data[1] & 0xffff; |
232f6502 | 281 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
5a5614cb | 282 | outw(cmReg.value, chan_iobase + REG_C0M); |
0c988d00 | 283 | |
5044a2c0 | 284 | /* Load the pre-load register 1 high word */ |
5a5614cb HS |
285 | val = (data[3] >> 16) & 0xffff; |
286 | outw(val, chan_iobase + REG_C0H); | |
0c988d00 | 287 | |
5044a2c0 | 288 | /* Load the pre-load register 1 low word */ |
5a5614cb HS |
289 | val = data[3] & 0xffff; |
290 | outw(val, chan_iobase + REG_C0L); | |
0c988d00 | 291 | |
232f6502 | 292 | /* Write the Counter Control Register */ |
5a5614cb HS |
293 | if (data[4]) { |
294 | val = data[4] & 0xffff; | |
295 | outw(val, chan_iobase + REG_C0C); | |
0c988d00 EW |
296 | } |
297 | break; | |
298 | ||
299 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
300 | /* | |
301 | data[0]: Application Type | |
302 | data[1]: Counter Mode Register Value | |
303 | data[2]: Pre-load Register 0 Value | |
304 | data[3]: Pre-load Register 1 Value | |
305 | data[4]: Conter Control Register | |
306 | */ | |
675f98f1 | 307 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 308 | |
232f6502 | 309 | /* Set Counter Mode Register */ |
5a5614cb | 310 | cmReg.value = data[1] & 0xffff; |
232f6502 | 311 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
5a5614cb | 312 | outw(cmReg.value, chan_iobase + REG_C0M); |
0c988d00 | 313 | |
5044a2c0 | 314 | /* Load the pre-load register 0 high word */ |
5a5614cb HS |
315 | val = (data[2] >> 16) & 0xffff; |
316 | outw(val, chan_iobase + REG_C0H); | |
0c988d00 | 317 | |
5044a2c0 | 318 | /* Load the pre-load register 0 low word */ |
5a5614cb HS |
319 | val = data[2] & 0xffff; |
320 | outw(val, chan_iobase + REG_C0L); | |
0c988d00 | 321 | |
232f6502 | 322 | /* Set Counter Mode Register */ |
5a5614cb | 323 | cmReg.value = data[1] & 0xffff; |
232f6502 | 324 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
5a5614cb | 325 | outw(cmReg.value, chan_iobase + REG_C0M); |
0c988d00 | 326 | |
5044a2c0 | 327 | /* Load the pre-load register 1 high word */ |
5a5614cb HS |
328 | val = (data[3] >> 16) & 0xffff; |
329 | outw(val, chan_iobase + REG_C0H); | |
0c988d00 | 330 | |
5044a2c0 | 331 | /* Load the pre-load register 1 low word */ |
5a5614cb HS |
332 | val = data[3] & 0xffff; |
333 | outw(val, chan_iobase + REG_C0L); | |
0c988d00 | 334 | |
232f6502 | 335 | /* Write the Counter Control Register */ |
5a5614cb HS |
336 | if (data[4]) { |
337 | val = data[4] & 0xffff; | |
338 | outw(val, chan_iobase + REG_C0C); | |
0c988d00 EW |
339 | } |
340 | break; | |
341 | ||
342 | default: | |
0c988d00 | 343 | return -EINVAL; |
0c988d00 EW |
344 | } |
345 | ||
346 | return insn->n; | |
347 | } | |
348 | ||
0a85b6f0 | 349 | static int s526_gpct_winsn(struct comedi_device *dev, |
5c813bb1 HS |
350 | struct comedi_subdevice *s, |
351 | struct comedi_insn *insn, | |
0a85b6f0 | 352 | unsigned int *data) |
0c988d00 | 353 | { |
5f221062 | 354 | struct s526_private *devpriv = dev->private; |
43a35276 | 355 | unsigned int chan = CR_CHAN(insn->chanspec); |
5c813bb1 HS |
356 | unsigned long chan_iobase = dev->iobase + chan * 8; |
357 | ||
358 | inw(chan_iobase + REG_C0M); /* Is this read required? */ | |
0c988d00 | 359 | |
232f6502 | 360 | /* Check what Application of Counter this channel is configured for */ |
675f98f1 HS |
361 | switch (devpriv->gpct_config[chan]) { |
362 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
0c988d00 EW |
363 | /* data[0] contains the PULSE_WIDTH |
364 | data[1] contains the PULSE_PERIOD | |
365 | @pre PULSE_PERIOD > PULSE_WIDTH > 0 | |
366 | The above periods must be expressed as a multiple of the | |
367 | pulse frequency on the selected source | |
368 | */ | |
67f2021f | 369 | if ((data[1] <= data[0]) || !data[0]) |
0c988d00 | 370 | return -EINVAL; |
0c988d00 | 371 | |
5c813bb1 HS |
372 | /* Fall thru to write the PULSE_WIDTH */ |
373 | ||
675f98f1 HS |
374 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
375 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
5c813bb1 HS |
376 | outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H); |
377 | outw(data[0] & 0xffff, chan_iobase + REG_C0L); | |
0c988d00 | 378 | break; |
5c813bb1 HS |
379 | |
380 | default: | |
0c988d00 | 381 | return -EINVAL; |
0c988d00 | 382 | } |
5c813bb1 | 383 | |
0c988d00 EW |
384 | return insn->n; |
385 | } | |
386 | ||
387 | #define ISR_ADC_DONE 0x4 | |
0a85b6f0 MT |
388 | static int s526_ai_insn_config(struct comedi_device *dev, |
389 | struct comedi_subdevice *s, | |
390 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 391 | { |
5f221062 | 392 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
393 | int result = -EINVAL; |
394 | ||
395 | if (insn->n < 1) | |
396 | return result; | |
397 | ||
398 | result = insn->n; | |
399 | ||
400 | /* data[0] : channels was set in relevant bits. | |
401 | data[1] : delay | |
402 | */ | |
403 | /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to | |
404 | * enable channels here. The channel should be enabled in the | |
405 | * INSN_READ handler. */ | |
406 | ||
232f6502 | 407 | /* Enable ADC interrupt */ |
0171e6f5 | 408 | outw(ISR_ADC_DONE, dev->iobase + REG_IER); |
bda60cbf | 409 | devpriv->ai_config = (data[0] & 0x3ff) << 5; |
0c988d00 | 410 | if (data[1] > 0) |
bda60cbf | 411 | devpriv->ai_config |= 0x8000; /* set the delay */ |
0c988d00 | 412 | |
bda60cbf | 413 | devpriv->ai_config |= 0x0001; /* ADC start bit */ |
0c988d00 EW |
414 | |
415 | return result; | |
416 | } | |
417 | ||
043fff87 HS |
418 | static int s526_ai_eoc(struct comedi_device *dev, |
419 | struct comedi_subdevice *s, | |
420 | struct comedi_insn *insn, | |
421 | unsigned long context) | |
422 | { | |
423 | unsigned int status; | |
424 | ||
425 | status = inw(dev->iobase + REG_ISR); | |
426 | if (status & ISR_ADC_DONE) | |
427 | return 0; | |
428 | return -EBUSY; | |
429 | } | |
430 | ||
da91b269 | 431 | static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 432 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 433 | { |
5f221062 | 434 | struct s526_private *devpriv = dev->private; |
43a35276 | 435 | unsigned int chan = CR_CHAN(insn->chanspec); |
043fff87 | 436 | int n; |
0c988d00 EW |
437 | unsigned short value; |
438 | unsigned int d; | |
043fff87 | 439 | int ret; |
0c988d00 EW |
440 | |
441 | /* Set configured delay, enable channel for this channel only, | |
442 | * select "ADC read" channel, set "ADC start" bit. */ | |
bda60cbf HS |
443 | value = (devpriv->ai_config & 0x8000) | |
444 | ((1 << 5) << chan) | (chan << 1) | 0x0001; | |
0c988d00 EW |
445 | |
446 | /* convert n samples */ | |
447 | for (n = 0; n < insn->n; n++) { | |
448 | /* trigger conversion */ | |
0171e6f5 | 449 | outw(value, dev->iobase + REG_ADC); |
0c988d00 | 450 | |
0c988d00 | 451 | /* wait for conversion to end */ |
043fff87 HS |
452 | ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0); |
453 | if (ret) | |
454 | return ret; | |
455 | ||
456 | outw(ISR_ADC_DONE, dev->iobase + REG_ISR); | |
0c988d00 EW |
457 | |
458 | /* read data */ | |
0171e6f5 | 459 | d = inw(dev->iobase + REG_ADD); |
0c988d00 EW |
460 | |
461 | /* munge data */ | |
462 | data[n] = d ^ 0x8000; | |
463 | } | |
464 | ||
465 | /* return the number of samples read/written */ | |
466 | return n; | |
467 | } | |
468 | ||
6db4a1f5 HS |
469 | static int s526_ao_insn_write(struct comedi_device *dev, |
470 | struct comedi_subdevice *s, | |
471 | struct comedi_insn *insn, | |
472 | unsigned int *data) | |
0c988d00 | 473 | { |
43a35276 | 474 | unsigned int chan = CR_CHAN(insn->chanspec); |
6db4a1f5 | 475 | unsigned int val = s->readback[chan]; |
43a35276 | 476 | int i; |
0c988d00 | 477 | |
6db4a1f5 | 478 | outw(chan << 1, dev->iobase + REG_DAC); |
0c988d00 | 479 | |
0c988d00 | 480 | for (i = 0; i < insn->n; i++) { |
6db4a1f5 HS |
481 | val = data[i]; |
482 | outw(val, dev->iobase + REG_ADD); | |
0171e6f5 | 483 | /* starts the D/A conversion */ |
6db4a1f5 | 484 | outw((chan << 1) | 1, dev->iobase + REG_DAC); |
0c988d00 | 485 | } |
6db4a1f5 | 486 | s->readback[chan] = val; |
0c988d00 | 487 | |
6db4a1f5 | 488 | return insn->n; |
0c988d00 EW |
489 | } |
490 | ||
0a85b6f0 MT |
491 | static int s526_dio_insn_bits(struct comedi_device *dev, |
492 | struct comedi_subdevice *s, | |
97f4289a HS |
493 | struct comedi_insn *insn, |
494 | unsigned int *data) | |
0c988d00 | 495 | { |
97f4289a | 496 | if (comedi_dio_update_state(s, data)) |
0171e6f5 | 497 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 | 498 | |
0171e6f5 | 499 | data[1] = inw(dev->iobase + REG_DIO) & 0xff; |
0c988d00 | 500 | |
a2714e3e | 501 | return insn->n; |
0c988d00 EW |
502 | } |
503 | ||
0a85b6f0 MT |
504 | static int s526_dio_insn_config(struct comedi_device *dev, |
505 | struct comedi_subdevice *s, | |
5dacadcc HS |
506 | struct comedi_insn *insn, |
507 | unsigned int *data) | |
0c988d00 | 508 | { |
43a35276 | 509 | unsigned int chan = CR_CHAN(insn->chanspec); |
5dacadcc HS |
510 | unsigned int mask; |
511 | int ret; | |
512 | ||
513 | if (chan < 4) | |
514 | mask = 0x0f; | |
515 | else | |
516 | mask = 0xf0; | |
517 | ||
518 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); | |
519 | if (ret) | |
520 | return ret; | |
521 | ||
522 | /* bit 10/11 set the group 1/2's mode */ | |
523 | if (s->io_bits & 0x0f) | |
524 | s->state |= (1 << 10); | |
525 | else | |
526 | s->state &= ~(1 << 10); | |
527 | if (s->io_bits & 0xf0) | |
528 | s->state |= (1 << 11); | |
529 | else | |
530 | s->state &= ~(1 << 11); | |
0c988d00 | 531 | |
0171e6f5 | 532 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 | 533 | |
5dacadcc | 534 | return insn->n; |
0c988d00 EW |
535 | } |
536 | ||
e9a4a7fb HS |
537 | static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
538 | { | |
5f221062 | 539 | struct s526_private *devpriv; |
e9a4a7fb | 540 | struct comedi_subdevice *s; |
8b6c5694 | 541 | int ret; |
e9a4a7fb | 542 | |
862755ec | 543 | ret = comedi_request_region(dev, it->options[0], 0x40); |
3cbc2810 HS |
544 | if (ret) |
545 | return ret; | |
e9a4a7fb | 546 | |
0bdab509 | 547 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
548 | if (!devpriv) |
549 | return -ENOMEM; | |
e9a4a7fb | 550 | |
8b6c5694 HS |
551 | ret = comedi_alloc_subdevices(dev, 4); |
552 | if (ret) | |
553 | return ret; | |
e9a4a7fb | 554 | |
97073c05 | 555 | s = &dev->subdevices[0]; |
e9a4a7fb HS |
556 | /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ |
557 | s->type = COMEDI_SUBD_COUNTER; | |
558 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; | |
3d9083b2 | 559 | s->n_chan = 4; |
e9a4a7fb HS |
560 | s->maxdata = 0x00ffffff; /* 24 bit counter */ |
561 | s->insn_read = s526_gpct_rinsn; | |
562 | s->insn_config = s526_gpct_insn_config; | |
563 | s->insn_write = s526_gpct_winsn; | |
564 | ||
97073c05 | 565 | s = &dev->subdevices[1]; |
e9a4a7fb HS |
566 | /* analog input subdevice */ |
567 | s->type = COMEDI_SUBD_AI; | |
e9a4a7fb HS |
568 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
569 | /* channels 0 to 7 are the regular differential inputs */ | |
570 | /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ | |
571 | s->n_chan = 10; | |
572 | s->maxdata = 0xffff; | |
573 | s->range_table = &range_bipolar10; | |
2c781789 | 574 | s->len_chanlist = 16; |
e9a4a7fb HS |
575 | s->insn_read = s526_ai_rinsn; |
576 | s->insn_config = s526_ai_insn_config; | |
577 | ||
97073c05 | 578 | s = &dev->subdevices[2]; |
e9a4a7fb HS |
579 | /* analog output subdevice */ |
580 | s->type = COMEDI_SUBD_AO; | |
581 | s->subdev_flags = SDF_WRITABLE; | |
582 | s->n_chan = 4; | |
583 | s->maxdata = 0xffff; | |
584 | s->range_table = &range_bipolar10; | |
6db4a1f5 HS |
585 | s->insn_write = s526_ao_insn_write; |
586 | s->insn_read = comedi_readback_insn_read; | |
587 | ||
588 | ret = comedi_alloc_subdev_readback(s); | |
589 | if (ret) | |
590 | return ret; | |
e9a4a7fb | 591 | |
97073c05 | 592 | s = &dev->subdevices[3]; |
e9a4a7fb | 593 | /* digital i/o subdevice */ |
3d9083b2 HS |
594 | s->type = COMEDI_SUBD_DIO; |
595 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
596 | s->n_chan = 8; | |
597 | s->maxdata = 1; | |
598 | s->range_table = &range_digital; | |
599 | s->insn_bits = s526_dio_insn_bits; | |
600 | s->insn_config = s526_dio_insn_config; | |
e9a4a7fb | 601 | |
fb780d21 | 602 | return 0; |
e9a4a7fb HS |
603 | } |
604 | ||
294f930d | 605 | static struct comedi_driver s526_driver = { |
e9a4a7fb HS |
606 | .driver_name = "s526", |
607 | .module = THIS_MODULE, | |
608 | .attach = s526_attach, | |
21208519 | 609 | .detach = comedi_legacy_detach, |
e9a4a7fb | 610 | }; |
294f930d | 611 | module_comedi_driver(s526_driver); |
90f703d3 AT |
612 | |
613 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
614 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
615 | MODULE_LICENSE("GPL"); |