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 { |
c9c62f4e | 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 | ||
0c988d00 EW |
200 | /* this structure is for data unique to this hardware driver. If |
201 | several hardware drivers keep similar information in this structure, | |
c9c62f4e XF |
202 | feel free to suggest moving the variable to the struct comedi_device |
203 | struct. | |
204 | */ | |
6dc1ece0 | 205 | struct s526_private { |
790c5541 | 206 | unsigned int ao_readback[2]; |
39f76660 | 207 | struct s526GPCTConfig s526_gpct_config[4]; |
0c988d00 | 208 | unsigned short s526_ai_config; |
6dc1ece0 BP |
209 | }; |
210 | ||
0a85b6f0 MT |
211 | static int s526_gpct_rinsn(struct comedi_device *dev, |
212 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
213 | unsigned int *data) | |
0c988d00 | 214 | { |
232f6502 | 215 | int i; /* counts the Data */ |
0c988d00 EW |
216 | int counter_channel = CR_CHAN(insn->chanspec); |
217 | unsigned short datalow; | |
218 | unsigned short datahigh; | |
219 | ||
232f6502 | 220 | /* Read the low word first */ |
0c988d00 | 221 | for (i = 0; i < insn->n; i++) { |
36fe5d26 HS |
222 | datalow = inw(dev->iobase + REG_C0L + counter_channel * 8); |
223 | datahigh = inw(dev->iobase + REG_C0H + counter_channel * 8); | |
0c988d00 EW |
224 | data[i] = (int)(datahigh & 0x00FF); |
225 | data[i] = (data[i] << 16) | (datalow & 0xFFFF); | |
0c988d00 EW |
226 | } |
227 | return i; | |
228 | } | |
229 | ||
0a85b6f0 MT |
230 | static int s526_gpct_insn_config(struct comedi_device *dev, |
231 | struct comedi_subdevice *s, | |
232 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 233 | { |
5f221062 | 234 | struct s526_private *devpriv = dev->private; |
232f6502 | 235 | int subdev_channel = CR_CHAN(insn->chanspec); /* Unpack chanspec */ |
0c988d00 | 236 | int i; |
790c5541 | 237 | short value; |
ca98ee7b | 238 | union cmReg cmReg; |
0c988d00 | 239 | |
898143bd | 240 | for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) |
b2abd982 | 241 | devpriv->s526_gpct_config[subdev_channel].data[i] = data[i]; |
0c988d00 | 242 | |
232f6502 BP |
243 | /* Check what type of Counter the user requested, data[0] contains */ |
244 | /* the Application type */ | |
b2abd982 | 245 | switch (data[0]) { |
0c988d00 EW |
246 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
247 | /* | |
248 | data[0]: Application Type | |
249 | data[1]: Counter Mode Register Value | |
250 | data[2]: Pre-load Register Value | |
251 | data[3]: Conter Control Register | |
252 | */ | |
c9c62f4e | 253 | printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring Encoder\n"); |
0c988d00 | 254 | devpriv->s526_gpct_config[subdev_channel].app = |
0a85b6f0 | 255 | PositionMeasurement; |
0c988d00 | 256 | |
232f6502 | 257 | #if 0 |
0a85b6f0 MT |
258 | /* Example of Counter Application */ |
259 | /* One-shot (software trigger) */ | |
260 | cmReg.reg.coutSource = 0; /* out RCAP */ | |
261 | cmReg.reg.coutPolarity = 1; /* Polarity inverted */ | |
c9c62f4e | 262 | cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */ |
0a85b6f0 MT |
263 | cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */ |
264 | cmReg.reg.ctEnableCtrl = 2; /* Hardware */ | |
265 | cmReg.reg.clockSource = 2; /* Internal */ | |
266 | cmReg.reg.countDir = 1; /* Down */ | |
267 | cmReg.reg.countDirCtrl = 1; /* Software */ | |
268 | cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */ | |
269 | cmReg.reg.preloadRegSel = 0; /* PR0 */ | |
270 | cmReg.reg.reserved = 0; | |
0c988d00 | 271 | |
36fe5d26 | 272 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0a85b6f0 | 273 | |
36fe5d26 HS |
274 | outw(0x0001, dev->iobase + REG_C0H + subdev_channel * 8); |
275 | outw(0x3C68, dev->iobase + REG_C0L + subdev_channel * 8); | |
0a85b6f0 | 276 | |
c9c62f4e | 277 | /* Reset the counter */ |
36fe5d26 | 278 | outw(0x8000, dev->iobase + REG_C0C + subdev_channel * 8); |
c9c62f4e | 279 | /* Load the counter from PR0 */ |
36fe5d26 | 280 | outw(0x4000, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 | 281 | |
c9c62f4e | 282 | /* Reset RCAP (fires one-shot) */ |
36fe5d26 | 283 | outw(0x0008, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 | 284 | |
232f6502 | 285 | #endif |
0c988d00 EW |
286 | |
287 | #if 1 | |
232f6502 | 288 | /* Set Counter Mode Register */ |
b2abd982 | 289 | cmReg.value = data[1] & 0xFFFF; |
36fe5d26 | 290 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 291 | |
232f6502 | 292 | /* Reset the counter if it is software preload */ |
0c988d00 | 293 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 294 | /* Reset the counter */ |
36fe5d26 | 295 | outw(0x8000, dev->iobase + REG_C0C + subdev_channel * 8); |
c9c62f4e | 296 | /* Load the counter from PR0 |
36fe5d26 | 297 | * outw(0x4000, dev->iobase + REG_C0C + subdev_channel * 8); |
c9c62f4e | 298 | */ |
0c988d00 EW |
299 | } |
300 | #else | |
c9c62f4e XF |
301 | /* 0 quadrature, 1 software control */ |
302 | cmReg.reg.countDirCtrl = 0; | |
0c988d00 | 303 | |
232f6502 | 304 | /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */ |
b2abd982 | 305 | if (data[1] == GPCT_X2) |
0c988d00 | 306 | cmReg.reg.clockSource = 1; |
b2abd982 | 307 | else if (data[1] == GPCT_X4) |
0c988d00 | 308 | cmReg.reg.clockSource = 2; |
c9c62f4e | 309 | else |
0c988d00 | 310 | cmReg.reg.clockSource = 0; |
0c988d00 | 311 | |
232f6502 | 312 | /* When to take into account the indexpulse: */ |
b2abd982 HS |
313 | /*if (data[2] == GPCT_IndexPhaseLowLow) { |
314 | } else if (data[2] == GPCT_IndexPhaseLowHigh) { | |
315 | } else if (data[2] == GPCT_IndexPhaseHighLow) { | |
316 | } else if (data[2] == GPCT_IndexPhaseHighHigh) { | |
c9c62f4e | 317 | }*/ |
232f6502 | 318 | /* Take into account the index pulse? */ |
b2abd982 | 319 | if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) |
c9c62f4e XF |
320 | /* Auto load with INDEX^ */ |
321 | cmReg.reg.autoLoadResetRcap = 4; | |
0c988d00 | 322 | |
232f6502 | 323 | /* Set Counter Mode Register */ |
b2abd982 | 324 | cmReg.value = (short)(data[1] & 0xFFFF); |
36fe5d26 | 325 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 326 | |
5044a2c0 | 327 | /* Load the pre-load register high word */ |
b2abd982 | 328 | value = (short)((data[2] >> 16) & 0xFFFF); |
36fe5d26 | 329 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0c988d00 | 330 | |
5044a2c0 | 331 | /* Load the pre-load register low word */ |
b2abd982 | 332 | value = (short)(data[2] & 0xFFFF); |
36fe5d26 | 333 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 334 | |
232f6502 | 335 | /* Write the Counter Control Register */ |
b2abd982 HS |
336 | if (data[3] != 0) { |
337 | value = (short)(data[3] & 0xFFFF); | |
36fe5d26 | 338 | outw(value, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 | 339 | } |
232f6502 | 340 | /* Reset the counter if it is software preload */ |
0c988d00 | 341 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 342 | /* Reset the counter */ |
36fe5d26 | 343 | outw(0x8000, dev->iobase + REG_C0C + subdev_channel * 8); |
c9c62f4e | 344 | /* Load the counter from PR0 */ |
36fe5d26 | 345 | outw(0x4000, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 EW |
346 | } |
347 | #endif | |
348 | break; | |
349 | ||
350 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
351 | /* | |
352 | data[0]: Application Type | |
353 | data[1]: Counter Mode Register Value | |
354 | data[2]: Pre-load Register 0 Value | |
355 | data[3]: Pre-load Register 1 Value | |
356 | data[4]: Conter Control Register | |
357 | */ | |
c9c62f4e | 358 | printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring SPG\n"); |
0c988d00 | 359 | devpriv->s526_gpct_config[subdev_channel].app = |
0a85b6f0 | 360 | SinglePulseGeneration; |
0c988d00 | 361 | |
232f6502 | 362 | /* Set Counter Mode Register */ |
b2abd982 | 363 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 364 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
36fe5d26 | 365 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 366 | |
5044a2c0 | 367 | /* Load the pre-load register 0 high word */ |
b2abd982 | 368 | value = (short)((data[2] >> 16) & 0xFFFF); |
36fe5d26 | 369 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0c988d00 | 370 | |
5044a2c0 | 371 | /* Load the pre-load register 0 low word */ |
b2abd982 | 372 | value = (short)(data[2] & 0xFFFF); |
36fe5d26 | 373 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 374 | |
232f6502 | 375 | /* Set Counter Mode Register */ |
b2abd982 | 376 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 377 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
36fe5d26 | 378 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 379 | |
5044a2c0 | 380 | /* Load the pre-load register 1 high word */ |
b2abd982 | 381 | value = (short)((data[3] >> 16) & 0xFFFF); |
36fe5d26 | 382 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0c988d00 | 383 | |
5044a2c0 | 384 | /* Load the pre-load register 1 low word */ |
b2abd982 | 385 | value = (short)(data[3] & 0xFFFF); |
36fe5d26 | 386 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 387 | |
232f6502 | 388 | /* Write the Counter Control Register */ |
b2abd982 HS |
389 | if (data[4] != 0) { |
390 | value = (short)(data[4] & 0xFFFF); | |
36fe5d26 | 391 | outw(value, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 EW |
392 | } |
393 | break; | |
394 | ||
395 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
396 | /* | |
397 | data[0]: Application Type | |
398 | data[1]: Counter Mode Register Value | |
399 | data[2]: Pre-load Register 0 Value | |
400 | data[3]: Pre-load Register 1 Value | |
401 | data[4]: Conter Control Register | |
402 | */ | |
c9c62f4e | 403 | printk(KERN_INFO "s526: GPCT_INSN_CONFIG: Configuring PTG\n"); |
0c988d00 | 404 | devpriv->s526_gpct_config[subdev_channel].app = |
0a85b6f0 | 405 | PulseTrainGeneration; |
0c988d00 | 406 | |
232f6502 | 407 | /* Set Counter Mode Register */ |
b2abd982 | 408 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 409 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
36fe5d26 | 410 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 411 | |
5044a2c0 | 412 | /* Load the pre-load register 0 high word */ |
b2abd982 | 413 | value = (short)((data[2] >> 16) & 0xFFFF); |
36fe5d26 | 414 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0c988d00 | 415 | |
5044a2c0 | 416 | /* Load the pre-load register 0 low word */ |
b2abd982 | 417 | value = (short)(data[2] & 0xFFFF); |
36fe5d26 | 418 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 419 | |
232f6502 | 420 | /* Set Counter Mode Register */ |
b2abd982 | 421 | cmReg.value = (short)(data[1] & 0xFFFF); |
232f6502 | 422 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
36fe5d26 | 423 | outw(cmReg.value, dev->iobase + REG_C0M + subdev_channel * 8); |
0c988d00 | 424 | |
5044a2c0 | 425 | /* Load the pre-load register 1 high word */ |
b2abd982 | 426 | value = (short)((data[3] >> 16) & 0xFFFF); |
36fe5d26 | 427 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0c988d00 | 428 | |
5044a2c0 | 429 | /* Load the pre-load register 1 low word */ |
b2abd982 | 430 | value = (short)(data[3] & 0xFFFF); |
36fe5d26 | 431 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 432 | |
232f6502 | 433 | /* Write the Counter Control Register */ |
b2abd982 HS |
434 | if (data[4] != 0) { |
435 | value = (short)(data[4] & 0xFFFF); | |
36fe5d26 | 436 | outw(value, dev->iobase + REG_C0C + subdev_channel * 8); |
0c988d00 EW |
437 | } |
438 | break; | |
439 | ||
440 | default: | |
c9c62f4e | 441 | printk(KERN_ERR "s526: unsupported GPCT_insn_config\n"); |
0c988d00 EW |
442 | return -EINVAL; |
443 | break; | |
444 | } | |
445 | ||
446 | return insn->n; | |
447 | } | |
448 | ||
0a85b6f0 MT |
449 | static int s526_gpct_winsn(struct comedi_device *dev, |
450 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
451 | unsigned int *data) | |
0c988d00 | 452 | { |
5f221062 | 453 | struct s526_private *devpriv = dev->private; |
232f6502 | 454 | int subdev_channel = CR_CHAN(insn->chanspec); /* Unpack chanspec */ |
790c5541 | 455 | short value; |
ca98ee7b | 456 | union cmReg cmReg; |
0c988d00 | 457 | |
c9c62f4e XF |
458 | printk(KERN_INFO "s526: GPCT_INSN_WRITE on channel %d\n", |
459 | subdev_channel); | |
36fe5d26 | 460 | cmReg.value = inw(dev->iobase + REG_C0M + subdev_channel * 8); |
c9c62f4e | 461 | printk(KERN_INFO "s526: Counter Mode Register: %x\n", cmReg.value); |
232f6502 | 462 | /* Check what Application of Counter this channel is configured for */ |
0c988d00 EW |
463 | switch (devpriv->s526_gpct_config[subdev_channel].app) { |
464 | case PositionMeasurement: | |
c9c62f4e | 465 | printk(KERN_INFO "S526: INSN_WRITE: PM\n"); |
36fe5d26 HS |
466 | outw(0xFFFF & ((*data) >> 16), dev->iobase + REG_C0H + |
467 | subdev_channel * 8); | |
468 | outw(0xFFFF & (*data), dev->iobase + REG_C0L + subdev_channel * 8); | |
0c988d00 EW |
469 | break; |
470 | ||
471 | case SinglePulseGeneration: | |
c9c62f4e | 472 | printk(KERN_INFO "S526: INSN_WRITE: SPG\n"); |
36fe5d26 HS |
473 | outw(0xFFFF & ((*data) >> 16), dev->iobase + REG_C0H + |
474 | subdev_channel * 8); | |
475 | outw(0xFFFF & (*data), dev->iobase + REG_C0L + subdev_channel * 8); | |
0c988d00 EW |
476 | break; |
477 | ||
478 | case PulseTrainGeneration: | |
479 | /* data[0] contains the PULSE_WIDTH | |
480 | data[1] contains the PULSE_PERIOD | |
481 | @pre PULSE_PERIOD > PULSE_WIDTH > 0 | |
482 | The above periods must be expressed as a multiple of the | |
483 | pulse frequency on the selected source | |
484 | */ | |
c9c62f4e | 485 | printk(KERN_INFO "S526: INSN_WRITE: PTG\n"); |
b2abd982 | 486 | if ((data[1] > data[0]) && (data[0] > 0)) { |
0c988d00 | 487 | (devpriv->s526_gpct_config[subdev_channel]).data[0] = |
b2abd982 | 488 | data[0]; |
0c988d00 | 489 | (devpriv->s526_gpct_config[subdev_channel]).data[1] = |
b2abd982 | 490 | data[1]; |
0c988d00 | 491 | } else { |
c9c62f4e | 492 | printk(KERN_ERR "s526: INSN_WRITE: PTG: Problem with Pulse params -> %d %d\n", |
b2abd982 | 493 | data[0], data[1]); |
0c988d00 EW |
494 | return -EINVAL; |
495 | } | |
496 | ||
0a85b6f0 | 497 | value = (short)((*data >> 16) & 0xFFFF); |
36fe5d26 | 498 | outw(value, dev->iobase + REG_C0H + subdev_channel * 8); |
0a85b6f0 | 499 | value = (short)(*data & 0xFFFF); |
36fe5d26 | 500 | outw(value, dev->iobase + REG_C0L + subdev_channel * 8); |
0c988d00 | 501 | break; |
232f6502 | 502 | default: /* Impossible */ |
0a85b6f0 MT |
503 | printk |
504 | ("s526: INSN_WRITE: Functionality %d not implemented yet\n", | |
505 | devpriv->s526_gpct_config[subdev_channel].app); | |
0c988d00 EW |
506 | return -EINVAL; |
507 | break; | |
508 | } | |
232f6502 | 509 | /* return the number of samples written */ |
0c988d00 EW |
510 | return insn->n; |
511 | } | |
512 | ||
513 | #define ISR_ADC_DONE 0x4 | |
0a85b6f0 MT |
514 | static int s526_ai_insn_config(struct comedi_device *dev, |
515 | struct comedi_subdevice *s, | |
516 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 517 | { |
5f221062 | 518 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
519 | int result = -EINVAL; |
520 | ||
521 | if (insn->n < 1) | |
522 | return result; | |
523 | ||
524 | result = insn->n; | |
525 | ||
526 | /* data[0] : channels was set in relevant bits. | |
527 | data[1] : delay | |
528 | */ | |
529 | /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to | |
530 | * enable channels here. The channel should be enabled in the | |
531 | * INSN_READ handler. */ | |
532 | ||
232f6502 | 533 | /* Enable ADC interrupt */ |
0171e6f5 | 534 | outw(ISR_ADC_DONE, dev->iobase + REG_IER); |
0c988d00 EW |
535 | devpriv->s526_ai_config = (data[0] & 0x3FF) << 5; |
536 | if (data[1] > 0) | |
232f6502 | 537 | devpriv->s526_ai_config |= 0x8000; /* set the delay */ |
0c988d00 | 538 | |
232f6502 | 539 | devpriv->s526_ai_config |= 0x0001; /* ADC start bit. */ |
0c988d00 EW |
540 | |
541 | return result; | |
542 | } | |
543 | ||
544 | /* | |
545 | * "instructions" read/write data in "one-shot" or "software-triggered" | |
546 | * mode. | |
547 | */ | |
da91b269 | 548 | static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 549 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 550 | { |
5f221062 | 551 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
552 | int n, i; |
553 | int chan = CR_CHAN(insn->chanspec); | |
554 | unsigned short value; | |
555 | unsigned int d; | |
556 | unsigned int status; | |
557 | ||
558 | /* Set configured delay, enable channel for this channel only, | |
559 | * select "ADC read" channel, set "ADC start" bit. */ | |
560 | value = (devpriv->s526_ai_config & 0x8000) | | |
0a85b6f0 | 561 | ((1 << 5) << chan) | (chan << 1) | 0x0001; |
0c988d00 EW |
562 | |
563 | /* convert n samples */ | |
564 | for (n = 0; n < insn->n; n++) { | |
565 | /* trigger conversion */ | |
0171e6f5 | 566 | outw(value, dev->iobase + REG_ADC); |
0c988d00 EW |
567 | |
568 | #define TIMEOUT 100 | |
569 | /* wait for conversion to end */ | |
570 | for (i = 0; i < TIMEOUT; i++) { | |
0171e6f5 | 571 | status = inw(dev->iobase + REG_ISR); |
0c988d00 | 572 | if (status & ISR_ADC_DONE) { |
0171e6f5 | 573 | outw(ISR_ADC_DONE, dev->iobase + REG_ISR); |
0c988d00 EW |
574 | break; |
575 | } | |
576 | } | |
577 | if (i == TIMEOUT) { | |
5f74ea14 | 578 | /* printk() should be used instead of printk() |
0c988d00 | 579 | * whenever the code can be called from real-time. */ |
c9c62f4e | 580 | printk(KERN_ERR "s526: ADC(0x%04x) timeout\n", |
0171e6f5 | 581 | inw(dev->iobase + REG_ISR)); |
0c988d00 EW |
582 | return -ETIMEDOUT; |
583 | } | |
584 | ||
585 | /* read data */ | |
0171e6f5 | 586 | d = inw(dev->iobase + REG_ADD); |
0c988d00 EW |
587 | |
588 | /* munge data */ | |
589 | data[n] = d ^ 0x8000; | |
590 | } | |
591 | ||
592 | /* return the number of samples read/written */ | |
593 | return n; | |
594 | } | |
595 | ||
da91b269 | 596 | static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 597 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 598 | { |
5f221062 | 599 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
600 | int i; |
601 | int chan = CR_CHAN(insn->chanspec); | |
602 | unsigned short val; | |
603 | ||
0c988d00 | 604 | val = chan << 1; |
232f6502 | 605 | /* outw(val, dev->iobase + REG_DAC); */ |
0171e6f5 | 606 | outw(val, dev->iobase + REG_DAC); |
0c988d00 EW |
607 | |
608 | /* Writing a list of values to an AO channel is probably not | |
609 | * very useful, but that's how the interface is defined. */ | |
610 | for (i = 0; i < insn->n; i++) { | |
611 | /* a typical programming sequence */ | |
c9c62f4e XF |
612 | /* write the data to preload register |
613 | * outw(data[i], dev->iobase + REG_ADD); | |
614 | */ | |
615 | /* write the data to preload register */ | |
0171e6f5 | 616 | outw(data[i], dev->iobase + REG_ADD); |
0c988d00 | 617 | devpriv->ao_readback[chan] = data[i]; |
232f6502 | 618 | /* outw(val + 1, dev->iobase + REG_DAC); starts the D/A conversion. */ |
0171e6f5 HS |
619 | /* starts the D/A conversion */ |
620 | outw(val + 1, dev->iobase + REG_DAC); | |
0c988d00 EW |
621 | } |
622 | ||
623 | /* return the number of samples read/written */ | |
624 | return i; | |
625 | } | |
626 | ||
627 | /* AO subdevices should have a read insn as well as a write insn. | |
628 | * Usually this means copying a value stored in devpriv. */ | |
da91b269 | 629 | static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 630 | struct comedi_insn *insn, unsigned int *data) |
0c988d00 | 631 | { |
5f221062 | 632 | struct s526_private *devpriv = dev->private; |
0c988d00 EW |
633 | int i; |
634 | int chan = CR_CHAN(insn->chanspec); | |
635 | ||
636 | for (i = 0; i < insn->n; i++) | |
637 | data[i] = devpriv->ao_readback[chan]; | |
638 | ||
639 | return i; | |
640 | } | |
641 | ||
642 | /* DIO devices are slightly special. Although it is possible to | |
643 | * implement the insn_read/insn_write interface, it is much more | |
644 | * useful to applications if you implement the insn_bits interface. | |
645 | * This allows packed reading/writing of the DIO channels. The | |
646 | * comedi core can convert between insn_bits and insn_read/write */ | |
0a85b6f0 MT |
647 | static int s526_dio_insn_bits(struct comedi_device *dev, |
648 | struct comedi_subdevice *s, | |
649 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 | 650 | { |
0c988d00 EW |
651 | /* The insn data is a mask in data[0] and the new data |
652 | * in data[1], each channel cooresponding to a bit. */ | |
653 | if (data[0]) { | |
654 | s->state &= ~data[0]; | |
655 | s->state |= data[0] & data[1]; | |
656 | /* Write out the new digital output lines */ | |
0171e6f5 | 657 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 EW |
658 | } |
659 | ||
660 | /* on return, data[1] contains the value of the digital | |
661 | * input and output lines. */ | |
0171e6f5 | 662 | data[1] = inw(dev->iobase + REG_DIO) & 0xff; |
0c988d00 EW |
663 | /* or we could just return the software copy of the output values if |
664 | * it was a purely digital output subdevice */ | |
10f27014 | 665 | /* data[1]=s->state & 0xFF; */ |
0c988d00 | 666 | |
a2714e3e | 667 | return insn->n; |
0c988d00 EW |
668 | } |
669 | ||
0a85b6f0 MT |
670 | static int s526_dio_insn_config(struct comedi_device *dev, |
671 | struct comedi_subdevice *s, | |
672 | struct comedi_insn *insn, unsigned int *data) | |
0c988d00 EW |
673 | { |
674 | int chan = CR_CHAN(insn->chanspec); | |
10f27014 | 675 | int group, mask; |
0c988d00 | 676 | |
c9c62f4e | 677 | printk(KERN_INFO "S526 DIO insn_config\n"); |
0c988d00 | 678 | |
0c988d00 EW |
679 | /* The input or output configuration of each digital line is |
680 | * configured by a special insn_config instruction. chanspec | |
681 | * contains the channel to be changed, and data[0] contains the | |
682 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
683 | ||
10f27014 IA |
684 | group = chan >> 2; |
685 | mask = 0xF << (group << 2); | |
686 | switch (data[0]) { | |
687 | case INSN_CONFIG_DIO_OUTPUT: | |
c9c62f4e XF |
688 | /* bit 10/11 set the group 1/2's mode */ |
689 | s->state |= 1 << (group + 10); | |
10f27014 IA |
690 | s->io_bits |= mask; |
691 | break; | |
692 | case INSN_CONFIG_DIO_INPUT: | |
c9c62f4e | 693 | s->state &= ~(1 << (group + 10)); /* 1 is output, 0 is input. */ |
10f27014 IA |
694 | s->io_bits &= ~mask; |
695 | break; | |
696 | case INSN_CONFIG_DIO_QUERY: | |
697 | data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT; | |
698 | return insn->n; | |
699 | default: | |
700 | return -EINVAL; | |
0c988d00 | 701 | } |
0171e6f5 | 702 | outw(s->state, dev->iobase + REG_DIO); |
0c988d00 EW |
703 | |
704 | return 1; | |
705 | } | |
706 | ||
e9a4a7fb HS |
707 | static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
708 | { | |
5254cbe7 | 709 | const struct s526_board *board = comedi_board(dev); |
5f221062 | 710 | struct s526_private *devpriv; |
e9a4a7fb HS |
711 | struct comedi_subdevice *s; |
712 | int iobase; | |
8b6c5694 | 713 | int ret; |
e9a4a7fb HS |
714 | |
715 | printk(KERN_INFO "comedi%d: s526: ", dev->minor); | |
716 | ||
717 | iobase = it->options[0]; | |
5254cbe7 | 718 | if (!iobase || !request_region(iobase, S526_IOSIZE, board->name)) { |
e9a4a7fb HS |
719 | comedi_error(dev, "I/O port conflict"); |
720 | return -EIO; | |
721 | } | |
722 | dev->iobase = iobase; | |
723 | ||
724 | printk("iobase=0x%lx\n", dev->iobase); | |
725 | ||
5254cbe7 | 726 | dev->board_name = board->name; |
e9a4a7fb | 727 | |
5f221062 HS |
728 | ret = alloc_private(dev, sizeof(*devpriv)); |
729 | if (ret) | |
730 | return ret; | |
731 | devpriv = dev->private; | |
e9a4a7fb | 732 | |
8b6c5694 HS |
733 | ret = comedi_alloc_subdevices(dev, 4); |
734 | if (ret) | |
735 | return ret; | |
e9a4a7fb | 736 | |
97073c05 | 737 | s = &dev->subdevices[0]; |
e9a4a7fb HS |
738 | /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ |
739 | s->type = COMEDI_SUBD_COUNTER; | |
740 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; | |
741 | /* KG: What does SDF_LSAMPL (see multiq3.c) mean? */ | |
5254cbe7 | 742 | s->n_chan = board->gpct_chans; |
e9a4a7fb HS |
743 | s->maxdata = 0x00ffffff; /* 24 bit counter */ |
744 | s->insn_read = s526_gpct_rinsn; | |
745 | s->insn_config = s526_gpct_insn_config; | |
746 | s->insn_write = s526_gpct_winsn; | |
747 | ||
748 | /* Command are not implemented yet, however they are necessary to | |
749 | allocate the necessary memory for the comedi_async struct (used | |
750 | to trigger the GPCT in case of pulsegenerator function */ | |
751 | /* s->do_cmd = s526_gpct_cmd; */ | |
752 | /* s->do_cmdtest = s526_gpct_cmdtest; */ | |
753 | /* s->cancel = s526_gpct_cancel; */ | |
754 | ||
97073c05 | 755 | s = &dev->subdevices[1]; |
e9a4a7fb HS |
756 | /* dev->read_subdev=s; */ |
757 | /* analog input subdevice */ | |
758 | s->type = COMEDI_SUBD_AI; | |
759 | /* we support differential */ | |
760 | s->subdev_flags = SDF_READABLE | SDF_DIFF; | |
761 | /* channels 0 to 7 are the regular differential inputs */ | |
762 | /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */ | |
763 | s->n_chan = 10; | |
764 | s->maxdata = 0xffff; | |
765 | s->range_table = &range_bipolar10; | |
766 | s->len_chanlist = 16; /* This is the maximum chanlist length that | |
767 | the board can handle */ | |
768 | s->insn_read = s526_ai_rinsn; | |
769 | s->insn_config = s526_ai_insn_config; | |
770 | ||
97073c05 | 771 | s = &dev->subdevices[2]; |
e9a4a7fb HS |
772 | /* analog output subdevice */ |
773 | s->type = COMEDI_SUBD_AO; | |
774 | s->subdev_flags = SDF_WRITABLE; | |
775 | s->n_chan = 4; | |
776 | s->maxdata = 0xffff; | |
777 | s->range_table = &range_bipolar10; | |
778 | s->insn_write = s526_ao_winsn; | |
779 | s->insn_read = s526_ao_rinsn; | |
780 | ||
97073c05 | 781 | s = &dev->subdevices[3]; |
e9a4a7fb | 782 | /* digital i/o subdevice */ |
5254cbe7 | 783 | if (board->have_dio) { |
e9a4a7fb HS |
784 | s->type = COMEDI_SUBD_DIO; |
785 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
786 | s->n_chan = 8; | |
787 | s->maxdata = 1; | |
788 | s->range_table = &range_digital; | |
789 | s->insn_bits = s526_dio_insn_bits; | |
790 | s->insn_config = s526_dio_insn_config; | |
791 | } else { | |
792 | s->type = COMEDI_SUBD_UNUSED; | |
793 | } | |
794 | ||
795 | printk(KERN_INFO "attached\n"); | |
796 | ||
797 | return 1; | |
e9a4a7fb HS |
798 | } |
799 | ||
484ecc95 | 800 | static void s526_detach(struct comedi_device *dev) |
e9a4a7fb | 801 | { |
e9a4a7fb HS |
802 | if (dev->iobase > 0) |
803 | release_region(dev->iobase, S526_IOSIZE); | |
e9a4a7fb HS |
804 | } |
805 | ||
294f930d | 806 | static struct comedi_driver s526_driver = { |
e9a4a7fb HS |
807 | .driver_name = "s526", |
808 | .module = THIS_MODULE, | |
809 | .attach = s526_attach, | |
810 | .detach = s526_detach, | |
811 | .board_name = &s526_boards[0].name, | |
812 | .offset = sizeof(struct s526_board), | |
813 | .num_names = ARRAY_SIZE(s526_boards), | |
814 | }; | |
294f930d | 815 | module_comedi_driver(s526_driver); |
90f703d3 AT |
816 | |
817 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
818 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
819 | MODULE_LICENSE("GPL"); |