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 | ||
4b1d53f0 | 86 | struct counter_mode_register_t { |
c9c62f4e | 87 | #if defined(__LITTLE_ENDIAN_BITFIELD) |
0c988d00 EW |
88 | unsigned short coutSource:1; |
89 | unsigned short coutPolarity:1; | |
90 | unsigned short autoLoadResetRcap:3; | |
91 | unsigned short hwCtEnableSource:2; | |
92 | unsigned short ctEnableCtrl:2; | |
93 | unsigned short clockSource:2; | |
94 | unsigned short countDir:1; | |
95 | unsigned short countDirCtrl:1; | |
96 | unsigned short outputRegLatchCtrl:1; | |
97 | unsigned short preloadRegSel:1; | |
98 | unsigned short reserved:1; | |
2b0318a6 IA |
99 | #elif defined(__BIG_ENDIAN_BITFIELD) |
100 | unsigned short reserved:1; | |
101 | unsigned short preloadRegSel:1; | |
102 | unsigned short outputRegLatchCtrl:1; | |
103 | unsigned short countDirCtrl:1; | |
104 | unsigned short countDir:1; | |
105 | unsigned short clockSource:2; | |
106 | unsigned short ctEnableCtrl:2; | |
107 | unsigned short hwCtEnableSource:2; | |
108 | unsigned short autoLoadResetRcap:3; | |
109 | unsigned short coutPolarity:1; | |
110 | unsigned short coutSource:1; | |
111 | #else | |
112 | #error Unknown bit field order | |
113 | #endif | |
4b1d53f0 | 114 | }; |
0c988d00 | 115 | |
ca98ee7b | 116 | union cmReg { |
4b1d53f0 | 117 | struct counter_mode_register_t reg; |
0c988d00 | 118 | unsigned short value; |
ca98ee7b | 119 | }; |
0c988d00 EW |
120 | |
121 | #define MAX_GPCT_CONFIG_DATA 6 | |
122 | ||
123 | /* Different Application Classes for GPCT Subdevices */ | |
124 | /* The list is not exhaustive and needs discussion! */ | |
dfb0503e | 125 | enum S526_GPCT_APP_CLASS { |
0c988d00 EW |
126 | CountingAndTimeMeasurement, |
127 | SinglePulseGeneration, | |
128 | PulseTrainGeneration, | |
129 | PositionMeasurement, | |
130 | Miscellaneous | |
dfb0503e | 131 | }; |
0c988d00 EW |
132 | |
133 | /* Config struct for different GPCT subdevice Application Classes and | |
134 | their options | |
135 | */ | |
39f76660 | 136 | struct s526GPCTConfig { |
dfb0503e | 137 | enum S526_GPCT_APP_CLASS app; |
0c988d00 | 138 | int data[MAX_GPCT_CONFIG_DATA]; |
39f76660 | 139 | }; |
0c988d00 | 140 | |
6dc1ece0 | 141 | struct s526_private { |
790c5541 | 142 | unsigned int ao_readback[2]; |
39f76660 | 143 | struct s526GPCTConfig s526_gpct_config[4]; |
0c988d00 | 144 | unsigned short s526_ai_config; |
6dc1ece0 BP |
145 | }; |
146 | ||
0a85b6f0 | 147 | static int s526_gpct_rinsn(struct comedi_device *dev, |
2a29edf6 HS |
148 | struct comedi_subdevice *s, |
149 | struct comedi_insn *insn, | |
0a85b6f0 | 150 | unsigned int *data) |
0c988d00 | 151 | { |
43a35276 | 152 | unsigned int chan = CR_CHAN(insn->chanspec); |
2a29edf6 HS |
153 | unsigned long chan_iobase = dev->iobase + chan * 8; |
154 | unsigned int lo; | |
155 | unsigned int hi; | |
43a35276 | 156 | int i; |
0c988d00 | 157 | |
0c988d00 | 158 | for (i = 0; i < insn->n; i++) { |
2a29edf6 HS |
159 | /* Read the low word first */ |
160 | lo = inw(chan_iobase + REG_C0L) & 0xffff; | |
161 | hi = inw(chan_iobase + REG_C0H) & 0xff; | |
162 | ||
163 | data[i] = (hi << 16) | lo; | |
0c988d00 | 164 | } |
2a29edf6 HS |
165 | |
166 | return insn->n; | |
0c988d00 EW |
167 | } |
168 | ||
0a85b6f0 MT |
169 | static int s526_gpct_insn_config(struct comedi_device *dev, |
170 | struct comedi_subdevice *s, | |
171 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 172 | { |
5f221062 | 173 | struct s526_private *devpriv = dev->private; |
43a35276 | 174 | unsigned int chan = CR_CHAN(insn->chanspec); |
0c988d00 | 175 | int i; |
790c5541 | 176 | short value; |
ca98ee7b | 177 | union cmReg cmReg; |
0c988d00 | 178 | |
898143bd | 179 | for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) |
43a35276 | 180 | devpriv->s526_gpct_config[chan].data[i] = data[i]; |
0c988d00 | 181 | |
232f6502 BP |
182 | /* Check what type of Counter the user requested, data[0] contains */ |
183 | /* the Application type */ | |
b2abd982 | 184 | switch (data[0]) { |
0c988d00 EW |
185 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
186 | /* | |
187 | data[0]: Application Type | |
188 | data[1]: Counter Mode Register Value | |
189 | data[2]: Pre-load Register Value | |
190 | data[3]: Conter Control Register | |
191 | */ | |
43a35276 | 192 | devpriv->s526_gpct_config[chan].app = PositionMeasurement; |
0c988d00 | 193 | |
232f6502 | 194 | #if 0 |
0a85b6f0 MT |
195 | /* Example of Counter Application */ |
196 | /* One-shot (software trigger) */ | |
197 | cmReg.reg.coutSource = 0; /* out RCAP */ | |
198 | cmReg.reg.coutPolarity = 1; /* Polarity inverted */ | |
c9c62f4e | 199 | cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */ |
0a85b6f0 MT |
200 | cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ |
201 | cmReg.reg.ctEnableCtrl = 2; /* Hardware */ | |
202 | cmReg.reg.clockSource = 2; /* Internal */ | |
203 | cmReg.reg.countDir = 1; /* Down */ | |
204 | cmReg.reg.countDirCtrl = 1; /* Software */ | |
205 | cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ | |
206 | cmReg.reg.preloadRegSel = 0; /* PR0 */ | |
207 | cmReg.reg.reserved = 0; | |
0c988d00 | 208 | |
43a35276 | 209 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0a85b6f0 | 210 | |
43a35276 HS |
211 | outw(0x0001, dev->iobase + REG_C0H + chan * 8); |
212 | outw(0x3C68, dev->iobase + REG_C0L + chan * 8); | |
0a85b6f0 | 213 | |
c9c62f4e | 214 | /* Reset the counter */ |
43a35276 | 215 | outw(0x8000, dev->iobase + REG_C0C + chan * 8); |
c9c62f4e | 216 | /* Load the counter from PR0 */ |
43a35276 | 217 | outw(0x4000, dev->iobase + REG_C0C + chan * 8); |
0c988d00 | 218 | |
c9c62f4e | 219 | /* Reset RCAP (fires one-shot) */ |
43a35276 | 220 | outw(0x0008, dev->iobase + REG_C0C + chan * 8); |
0c988d00 | 221 | |
232f6502 | 222 | #endif |
0c988d00 EW |
223 | |
224 | #if 1 | |
232f6502 | 225 | /* Set Counter Mode Register */ |
b2abd982 | 226 | cmReg.value = data[1] & 0xFFFF; |
43a35276 | 227 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 228 | |
232f6502 | 229 | /* Reset the counter if it is software preload */ |
0c988d00 | 230 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 231 | /* Reset the counter */ |
43a35276 | 232 | outw(0x8000, dev->iobase + REG_C0C + chan * 8); |
c9c62f4e | 233 | /* Load the counter from PR0 |
43a35276 | 234 | * outw(0x4000, dev->iobase + REG_C0C + chan * 8); |
c9c62f4e | 235 | */ |
0c988d00 EW |
236 | } |
237 | #else | |
c9c62f4e XF |
238 | /* 0 quadrature, 1 software control */ |
239 | cmReg.reg.countDirCtrl = 0; | |
0c988d00 | 240 | |
232f6502 | 241 | /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ |
b2abd982 | 242 | if (data[1] == GPCT_X2) |
0c988d00 | 243 | cmReg.reg.clockSource = 1; |
b2abd982 | 244 | else if (data[1] == GPCT_X4) |
0c988d00 | 245 | cmReg.reg.clockSource = 2; |
c9c62f4e | 246 | else |
0c988d00 | 247 | cmReg.reg.clockSource = 0; |
0c988d00 | 248 | |
232f6502 | 249 | /* When to take into account the indexpulse: */ |
b2abd982 HS |
250 | /*if (data[2] == GPCT_IndexPhaseLowLow) { |
251 | } else if (data[2] == GPCT_IndexPhaseLowHigh) { | |
252 | } else if (data[2] == GPCT_IndexPhaseHighLow) { | |
253 | } else if (data[2] == GPCT_IndexPhaseHighHigh) { | |
c9c62f4e | 254 | }*/ |
232f6502 | 255 | /* Take into account the index pulse? */ |
b2abd982 | 256 | if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) |
c9c62f4e XF |
257 | /* Auto load with INDEX^ */ |
258 | cmReg.reg.autoLoadResetRcap = 4; | |
0c988d00 | 259 | |
232f6502 | 260 | /* Set Counter Mode Register */ |
b2abd982 | 261 | cmReg.value = (short)(data[1] & 0xFFFF); |
43a35276 | 262 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 263 | |
5044a2c0 | 264 | /* Load the pre-load register high word */ |
b2abd982 | 265 | value = (short)((data[2] >> 16) & 0xFFFF); |
43a35276 | 266 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0c988d00 | 267 | |
5044a2c0 | 268 | /* Load the pre-load register low word */ |
b2abd982 | 269 | value = (short)(data[2] & 0xFFFF); |
43a35276 | 270 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 271 | |
232f6502 | 272 | /* Write the Counter Control Register */ |
b2abd982 HS |
273 | if (data[3] != 0) { |
274 | value = (short)(data[3] & 0xFFFF); | |
43a35276 | 275 | outw(value, dev->iobase + REG_C0C + chan * 8); |
0c988d00 | 276 | } |
232f6502 | 277 | /* Reset the counter if it is software preload */ |
0c988d00 | 278 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 279 | /* Reset the counter */ |
43a35276 | 280 | outw(0x8000, dev->iobase + REG_C0C + chan * 8); |
c9c62f4e | 281 | /* Load the counter from PR0 */ |
43a35276 | 282 | outw(0x4000, dev->iobase + REG_C0C + chan * 8); |
0c988d00 EW |
283 | } |
284 | #endif | |
285 | break; | |
286 | ||
287 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
288 | /* | |
289 | data[0]: Application Type | |
290 | data[1]: Counter Mode Register Value | |
291 | data[2]: Pre-load Register 0 Value | |
292 | data[3]: Pre-load Register 1 Value | |
293 | data[4]: Conter Control Register | |
294 | */ | |
43a35276 | 295 | devpriv->s526_gpct_config[chan].app = SinglePulseGeneration; |
0c988d00 | 296 | |
232f6502 | 297 | /* Set Counter Mode Register */ |
b2abd982 | 298 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 299 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
43a35276 | 300 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 301 | |
5044a2c0 | 302 | /* Load the pre-load register 0 high word */ |
b2abd982 | 303 | value = (short)((data[2] >> 16) & 0xFFFF); |
43a35276 | 304 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0c988d00 | 305 | |
5044a2c0 | 306 | /* Load the pre-load register 0 low word */ |
b2abd982 | 307 | value = (short)(data[2] & 0xFFFF); |
43a35276 | 308 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 309 | |
232f6502 | 310 | /* Set Counter Mode Register */ |
b2abd982 | 311 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 312 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
43a35276 | 313 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 314 | |
5044a2c0 | 315 | /* Load the pre-load register 1 high word */ |
b2abd982 | 316 | value = (short)((data[3] >> 16) & 0xFFFF); |
43a35276 | 317 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0c988d00 | 318 | |
5044a2c0 | 319 | /* Load the pre-load register 1 low word */ |
b2abd982 | 320 | value = (short)(data[3] & 0xFFFF); |
43a35276 | 321 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 322 | |
232f6502 | 323 | /* Write the Counter Control Register */ |
b2abd982 HS |
324 | if (data[4] != 0) { |
325 | value = (short)(data[4] & 0xFFFF); | |
43a35276 | 326 | outw(value, dev->iobase + REG_C0C + chan * 8); |
0c988d00 EW |
327 | } |
328 | break; | |
329 | ||
330 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
331 | /* | |
332 | data[0]: Application Type | |
333 | data[1]: Counter Mode Register Value | |
334 | data[2]: Pre-load Register 0 Value | |
335 | data[3]: Pre-load Register 1 Value | |
336 | data[4]: Conter Control Register | |
337 | */ | |
43a35276 | 338 | devpriv->s526_gpct_config[chan].app = PulseTrainGeneration; |
0c988d00 | 339 | |
232f6502 | 340 | /* Set Counter Mode Register */ |
b2abd982 | 341 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 342 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
43a35276 | 343 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 344 | |
5044a2c0 | 345 | /* Load the pre-load register 0 high word */ |
b2abd982 | 346 | value = (short)((data[2] >> 16) & 0xFFFF); |
43a35276 | 347 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0c988d00 | 348 | |
5044a2c0 | 349 | /* Load the pre-load register 0 low word */ |
b2abd982 | 350 | value = (short)(data[2] & 0xFFFF); |
43a35276 | 351 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 352 | |
232f6502 | 353 | /* Set Counter Mode Register */ |
b2abd982 | 354 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 355 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
43a35276 | 356 | outw(cmReg.value, dev->iobase + REG_C0M + chan * 8); |
0c988d00 | 357 | |
5044a2c0 | 358 | /* Load the pre-load register 1 high word */ |
b2abd982 | 359 | value = (short)((data[3] >> 16) & 0xFFFF); |
43a35276 | 360 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0c988d00 | 361 | |
5044a2c0 | 362 | /* Load the pre-load register 1 low word */ |
b2abd982 | 363 | value = (short)(data[3] & 0xFFFF); |
43a35276 | 364 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 365 | |
232f6502 | 366 | /* Write the Counter Control Register */ |
b2abd982 HS |
367 | if (data[4] != 0) { |
368 | value = (short)(data[4] & 0xFFFF); | |
43a35276 | 369 | outw(value, dev->iobase + REG_C0C + chan * 8); |
0c988d00 EW |
370 | } |
371 | break; | |
372 | ||
373 | default: | |
0c988d00 EW |
374 | return -EINVAL; |
375 | break; | |
376 | } | |
377 | ||
378 | return insn->n; | |
379 | } | |
380 | ||
0a85b6f0 MT |
381 | static int s526_gpct_winsn(struct comedi_device *dev, |
382 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
383 | unsigned int *data) | |
0c988d00 | 384 | { |
5f221062 | 385 | struct s526_private *devpriv = dev->private; |
43a35276 | 386 | unsigned int chan = CR_CHAN(insn->chanspec); |
790c5541 | 387 | short value; |
ca98ee7b | 388 | union cmReg cmReg; |
0c988d00 | 389 | |
43a35276 | 390 | cmReg.value = inw(dev->iobase + REG_C0M + chan * 8); |
232f6502 | 391 | /* Check what Application of Counter this channel is configured for */ |
43a35276 | 392 | switch (devpriv->s526_gpct_config[chan].app) { |
0c988d00 | 393 | case PositionMeasurement: |
43a35276 HS |
394 | outw(0xFFFF & ((*data) >> 16), dev->iobase + REG_C0H + chan * 8); |
395 | outw(0xFFFF & (*data), dev->iobase + REG_C0L + chan * 8); | |
0c988d00 EW |
396 | break; |
397 | ||
398 | case SinglePulseGeneration: | |
43a35276 HS |
399 | outw(0xFFFF & ((*data) >> 16), dev->iobase + REG_C0H + chan * 8); |
400 | outw(0xFFFF & (*data), dev->iobase + REG_C0L + chan * 8); | |
0c988d00 EW |
401 | break; |
402 | ||
403 | case PulseTrainGeneration: | |
404 | /* data[0] contains the PULSE_WIDTH | |
405 | data[1] contains the PULSE_PERIOD | |
406 | @pre PULSE_PERIOD > PULSE_WIDTH > 0 | |
407 | The above periods must be expressed as a multiple of the | |
408 | pulse frequency on the selected source | |
409 | */ | |
b2abd982 | 410 | if ((data[1] > data[0]) && (data[0] > 0)) { |
43a35276 HS |
411 | (devpriv->s526_gpct_config[chan]).data[0] = data[0]; |
412 | (devpriv->s526_gpct_config[chan]).data[1] = data[1]; | |
0c988d00 | 413 | } else { |
0c988d00 EW |
414 | return -EINVAL; |
415 | } | |
416 | ||
0a85b6f0 | 417 | value = (short)((*data >> 16) & 0xFFFF); |
43a35276 | 418 | outw(value, dev->iobase + REG_C0H + chan * 8); |
0a85b6f0 | 419 | value = (short)(*data & 0xFFFF); |
43a35276 | 420 | outw(value, dev->iobase + REG_C0L + chan * 8); |
0c988d00 | 421 | break; |
232f6502 | 422 | default: /* Impossible */ |
0c988d00 EW |
423 | return -EINVAL; |
424 | break; | |
425 | } | |
232f6502 | 426 | /* return the number of samples written */ |
0c988d00 EW |
427 | return insn->n; |
428 | } | |
429 | ||
430 | #define ISR_ADC_DONE 0x4 | |
0a85b6f0 MT |
431 | static int s526_ai_insn_config(struct comedi_device *dev, |
432 | struct comedi_subdevice *s, | |
433 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 434 | { |
5f221062 | 435 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
436 | int result = -EINVAL; |
437 | ||
438 | if (insn->n < 1) | |
439 | return result; | |
440 | ||
441 | result = insn->n; | |
442 | ||
443 | /* data[0] : channels was set in relevant bits. | |
444 | data[1] : delay | |
445 | */ | |
446 | /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to | |
447 | * enable channels here. The channel should be enabled in the | |
448 | * INSN_READ handler. */ | |
449 | ||
232f6502 | 450 | /* Enable ADC interrupt */ |
0171e6f5 | 451 | outw(ISR_ADC_DONE, dev->iobase + REG_IER); |
0c988d00 EW |
452 | devpriv->s526_ai_config = (data[0] & 0x3FF) << 5; |
453 | if (data[1] > 0) | |
232f6502 | 454 | devpriv->s526_ai_config |= 0x8000; /* set the delay */ |
0c988d00 | 455 | |
232f6502 | 456 | devpriv->s526_ai_config |= 0x0001; /* ADC start bit. */ |
0c988d00 EW |
457 | |
458 | return result; | |
459 | } | |
460 | ||
da91b269 | 461 | static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 462 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 463 | { |
5f221062 | 464 | struct s526_private *devpriv = dev->private; |
43a35276 | 465 | unsigned int chan = CR_CHAN(insn->chanspec); |
0c988d00 | 466 | int n, i; |
0c988d00 EW |
467 | unsigned short value; |
468 | unsigned int d; | |
469 | unsigned int status; | |
470 | ||
471 | /* Set configured delay, enable channel for this channel only, | |
472 | * select "ADC read" channel, set "ADC start" bit. */ | |
473 | value = (devpriv->s526_ai_config & 0x8000) | | |
0a85b6f0 | 474 | ((1 << 5) << chan) | (chan << 1) | 0x0001; |
0c988d00 EW |
475 | |
476 | /* convert n samples */ | |
477 | for (n = 0; n < insn->n; n++) { | |
478 | /* trigger conversion */ | |
0171e6f5 | 479 | outw(value, dev->iobase + REG_ADC); |
0c988d00 EW |
480 | |
481 | #define TIMEOUT 100 | |
482 | /* wait for conversion to end */ | |
483 | for (i = 0; i < TIMEOUT; i++) { | |
0171e6f5 | 484 | status = inw(dev->iobase + REG_ISR); |
0c988d00 | 485 | if (status & ISR_ADC_DONE) { |
0171e6f5 | 486 | outw(ISR_ADC_DONE, dev->iobase + REG_ISR); |
0c988d00 EW |
487 | break; |
488 | } | |
489 | } | |
d160895f | 490 | if (i == TIMEOUT) |
0c988d00 | 491 | return -ETIMEDOUT; |
0c988d00 EW |
492 | |
493 | /* read data */ | |
0171e6f5 | 494 | d = inw(dev->iobase + REG_ADD); |
0c988d00 EW |
495 | |
496 | /* munge data */ | |
497 | data[n] = d ^ 0x8000; | |
498 | } | |
499 | ||
500 | /* return the number of samples read/written */ | |
501 | return n; | |
502 | } | |
503 | ||
da91b269 | 504 | static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 505 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 506 | { |
5f221062 | 507 | struct s526_private *devpriv = dev->private; |
43a35276 | 508 | unsigned int chan = CR_CHAN(insn->chanspec); |
0c988d00 | 509 | unsigned short val; |
43a35276 | 510 | int i; |
0c988d00 | 511 | |
0c988d00 | 512 | val = chan << 1; |
0171e6f5 | 513 | outw(val, dev->iobase + REG_DAC); |
0c988d00 | 514 | |
0c988d00 | 515 | for (i = 0; i < insn->n; i++) { |
0171e6f5 | 516 | outw(data[i], dev->iobase + REG_ADD); |
0c988d00 | 517 | devpriv->ao_readback[chan] = data[i]; |
0171e6f5 HS |
518 | /* starts the D/A conversion */ |
519 | outw(val + 1, dev->iobase + REG_DAC); | |
0c988d00 EW |
520 | } |
521 | ||
0c988d00 EW |
522 | return i; |
523 | } | |
524 | ||
da91b269 | 525 | static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 526 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 527 | { |
5f221062 | 528 | struct s526_private *devpriv = dev->private; |
43a35276 | 529 | unsigned int chan = CR_CHAN(insn->chanspec); |
0c988d00 | 530 | int i; |
0c988d00 EW |
531 | |
532 | for (i = 0; i < insn->n; i++) | |
533 | data[i] = devpriv->ao_readback[chan]; | |
534 | ||
535 | return i; | |
536 | } | |
537 | ||
0a85b6f0 MT |
538 | static int s526_dio_insn_bits(struct comedi_device *dev, |
539 | struct comedi_subdevice *s, | |
540 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 541 | { |
0c988d00 EW |
542 | if (data[0]) { |
543 | s->state &= ~data[0]; | |
544 | s->state |= data[0] & data[1]; | |
2c781789 | 545 | |
0171e6f5 | 546 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 EW |
547 | } |
548 | ||
0171e6f5 | 549 | data[1] = inw(dev->iobase + REG_DIO) & 0xff; |
0c988d00 | 550 | |
a2714e3e | 551 | return insn->n; |
0c988d00 EW |
552 | } |
553 | ||
0a85b6f0 MT |
554 | static int s526_dio_insn_config(struct comedi_device *dev, |
555 | struct comedi_subdevice *s, | |
556 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 557 | { |
43a35276 | 558 | unsigned int chan = CR_CHAN(insn->chanspec); |
10f27014 | 559 | int group, mask; |
0c988d00 | 560 | |
10f27014 IA |
561 | group = chan >> 2; |
562 | mask = 0xF << (group << 2); | |
563 | switch (data[0]) { | |
564 | case INSN_CONFIG_DIO_OUTPUT: | |
c9c62f4e XF |
565 | /* bit 10/11 set the group 1/2's mode */ |
566 | s->state |= 1 << (group + 10); | |
10f27014 IA |
567 | s->io_bits |= mask; |
568 | break; | |
569 | case INSN_CONFIG_DIO_INPUT: | |
c9c62f4e | 570 | s->state &= ~(1 << (group + 10)); /* 1 is output, 0 is input. */ |
10f27014 IA |
571 | s->io_bits &= ~mask; |
572 | break; | |
573 | case INSN_CONFIG_DIO_QUERY: | |
574 | data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT; | |
575 | return insn->n; | |
576 | default: | |
577 | return -EINVAL; | |
0c988d00 | 578 | } |
0171e6f5 | 579 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 EW |
580 | |
581 | return 1; | |
582 | } | |
583 | ||
e9a4a7fb HS |
584 | static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
585 | { | |
5f221062 | 586 | struct s526_private *devpriv; |
e9a4a7fb HS |
587 | struct comedi_subdevice *s; |
588 | int iobase; | |
8b6c5694 | 589 | int ret; |
e9a4a7fb | 590 | |
3d9083b2 HS |
591 | dev->board_name = dev->driver->driver_name; |
592 | ||
e9a4a7fb | 593 | iobase = it->options[0]; |
3d9083b2 | 594 | if (!iobase || !request_region(iobase, S526_IOSIZE, dev->board_name)) { |
e9a4a7fb HS |
595 | comedi_error(dev, "I/O port conflict"); |
596 | return -EIO; | |
597 | } | |
598 | dev->iobase = iobase; | |
599 | ||
5f221062 HS |
600 | ret = alloc_private(dev, sizeof(*devpriv)); |
601 | if (ret) | |
602 | return ret; | |
603 | devpriv = dev->private; | |
e9a4a7fb | 604 | |
8b6c5694 HS |
605 | ret = comedi_alloc_subdevices(dev, 4); |
606 | if (ret) | |
607 | return ret; | |
e9a4a7fb | 608 | |
97073c05 | 609 | s = &dev->subdevices[0]; |
e9a4a7fb HS |
610 | /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ |
611 | s->type = COMEDI_SUBD_COUNTER; | |
612 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; | |
3d9083b2 | 613 | s->n_chan = 4; |
e9a4a7fb HS |
614 | s->maxdata = 0x00ffffff; /* 24 bit counter */ |
615 | s->insn_read = s526_gpct_rinsn; | |
616 | s->insn_config = s526_gpct_insn_config; | |
617 | s->insn_write = s526_gpct_winsn; | |
618 | ||
97073c05 | 619 | s = &dev->subdevices[1]; |
e9a4a7fb HS |
620 | /* analog input subdevice */ |
621 | s->type = COMEDI_SUBD_AI; | |
e9a4a7fb HS |
622 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
623 | /* channels 0 to 7 are the regular differential inputs */ | |
624 | /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ | |
625 | s->n_chan = 10; | |
626 | s->maxdata = 0xffff; | |
627 | s->range_table = &range_bipolar10; | |
2c781789 | 628 | s->len_chanlist = 16; |
e9a4a7fb HS |
629 | s->insn_read = s526_ai_rinsn; |
630 | s->insn_config = s526_ai_insn_config; | |
631 | ||
97073c05 | 632 | s = &dev->subdevices[2]; |
e9a4a7fb HS |
633 | /* analog output subdevice */ |
634 | s->type = COMEDI_SUBD_AO; | |
635 | s->subdev_flags = SDF_WRITABLE; | |
636 | s->n_chan = 4; | |
637 | s->maxdata = 0xffff; | |
638 | s->range_table = &range_bipolar10; | |
639 | s->insn_write = s526_ao_winsn; | |
640 | s->insn_read = s526_ao_rinsn; | |
641 | ||
97073c05 | 642 | s = &dev->subdevices[3]; |
e9a4a7fb | 643 | /* digital i/o subdevice */ |
3d9083b2 HS |
644 | s->type = COMEDI_SUBD_DIO; |
645 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
646 | s->n_chan = 8; | |
647 | s->maxdata = 1; | |
648 | s->range_table = &range_digital; | |
649 | s->insn_bits = s526_dio_insn_bits; | |
650 | s->insn_config = s526_dio_insn_config; | |
e9a4a7fb | 651 | |
d160895f | 652 | dev_info(dev->class_dev, "%s attached\n", dev->board_name); |
e9a4a7fb HS |
653 | |
654 | return 1; | |
e9a4a7fb HS |
655 | } |
656 | ||
484ecc95 | 657 | static void s526_detach(struct comedi_device *dev) |
e9a4a7fb | 658 | { |
e9a4a7fb HS |
659 | if (dev->iobase > 0) |
660 | release_region(dev->iobase, S526_IOSIZE); | |
e9a4a7fb HS |
661 | } |
662 | ||
294f930d | 663 | static struct comedi_driver s526_driver = { |
e9a4a7fb HS |
664 | .driver_name = "s526", |
665 | .module = THIS_MODULE, | |
666 | .attach = s526_attach, | |
667 | .detach = s526_detach, | |
e9a4a7fb | 668 | }; |
294f930d | 669 | module_comedi_driver(s526_driver); |
90f703d3 AT |
670 | |
671 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
672 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
673 | MODULE_LICENSE("GPL"); |