Commit | Line | Data |
---|---|---|
0c988d00 | 1 | /* |
de332b11 HS |
2 | * 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 | */ | |
0c988d00 | 18 | |
0c988d00 | 19 | /* |
de332b11 HS |
20 | * Driver: s526 |
21 | * Description: Sensoray 526 driver | |
22 | * Devices: [Sensoray] 526 (s526) | |
23 | * Author: Richie | |
24 | * Everett Wang <everett.wang@everteq.com> | |
25 | * Updated: Thu, 14 Sep. 2006 | |
26 | * Status: experimental | |
27 | * | |
28 | * Encoder works | |
29 | * Analog input works | |
30 | * Analog output works | |
31 | * PWM output works | |
32 | * Commands are not supported yet. | |
33 | * | |
34 | * Configuration Options: | |
35 | * [0] - I/O port base address | |
36 | */ | |
0c988d00 | 37 | |
ce157f80 | 38 | #include <linux/module.h> |
0c988d00 | 39 | #include "../comedidev.h" |
2b0318a6 | 40 | #include <asm/byteorder.h> |
0c988d00 | 41 | |
537dd665 HS |
42 | /* |
43 | * Register I/O map | |
44 | */ | |
4e04fd32 HS |
45 | #define S526_TIMER_REG 0x00 |
46 | #define S526_TIMER_LOAD(x) (((x) & 0xff) << 8) | |
47 | #define S526_TIMER_MODE ((x) << 1) | |
48 | #define S526_TIMER_MANUAL S526_TIMER_MODE(0) | |
49 | #define S526_TIMER_AUTO S526_TIMER_MODE(1) | |
50 | #define S526_TIMER_RESTART BIT(0) | |
088c1ce0 HS |
51 | #define S526_WDOG_REG 0x02 |
52 | #define S526_WDOG_INVERTED BIT(4) | |
53 | #define S526_WDOG_ENA BIT(3) | |
54 | #define S526_WDOG_INTERVAL(x) (((x) & 0x7) << 0) | |
abbb6489 HS |
55 | #define S526_AO_CTRL_REG 0x04 |
56 | #define S526_AO_CTRL_RESET BIT(3) | |
57 | #define S526_AO_CTRL_CHAN(x) (((x) & 0x3) << 1) | |
58 | #define S526_AO_CTRL_START BIT(0) | |
fe79b3d0 HS |
59 | #define S526_AI_CTRL_REG 0x06 |
60 | #define S526_AI_CTRL_DELAY BIT(15) | |
61 | #define S526_AI_CTRL_CONV(x) (1 << (5 + ((x) & 0x9))) | |
62 | #define S526_AI_CTRL_READ(x) (((x) & 0xf) << 1) | |
63 | #define S526_AI_CTRL_START BIT(0) | |
15bccf2e HS |
64 | #define S526_AO_REG 0x08 |
65 | #define S526_AI_REG 0x08 | |
658441b4 HS |
66 | #define S526_DIO_CTRL_REG 0x0a |
67 | #define S526_DIO_CTRL_DIO3_NEG BIT(15) /* irq on DIO3 neg/pos edge */ | |
68 | #define S526_DIO_CTRL_DIO2_NEG BIT(14) /* irq on DIO2 neg/pos edge */ | |
69 | #define S526_DIO_CTRL_DIO1_NEG BIT(13) /* irq on DIO1 neg/pos edge */ | |
70 | #define S526_DIO_CTRL_DIO0_NEG BIT(12) /* irq on DIO0 neg/pos edge */ | |
71 | #define S526_DIO_CTRL_GRP2_OUT BIT(11) | |
72 | #define S526_DIO_CTRL_GRP1_OUT BIT(10) | |
73 | #define S526_DIO_CTRL_GRP2_NEG BIT(8) /* irq on DIO[4-7] neg/pos edge */ | |
8a5d6d2e HS |
74 | #define S526_INT_ENA_REG 0x0c |
75 | #define S526_INT_STATUS_REG 0x0e | |
76 | #define S526_INT_DIO(x) BIT(8 + ((x) & 0x7)) | |
77 | #define S526_INT_EEPROM BIT(7) /* status only */ | |
78 | #define S526_INT_CNTR(x) BIT(3 + (3 - ((x) & 0x3))) | |
79 | #define S526_INT_AI BIT(2) | |
80 | #define S526_INT_AO BIT(1) | |
81 | #define S526_INT_TIMER BIT(0) | |
64fe38f4 HS |
82 | #define S526_MISC_REG 0x10 |
83 | #define S526_MISC_LED_OFF BIT(0) | |
1d0d1c00 HS |
84 | #define S526_GPCT_LSB_REG(x) (0x12 + ((x) * 8)) |
85 | #define S526_GPCT_MSB_REG(x) (0x14 + ((x) * 8)) | |
86 | #define S526_GPCT_MODE_REG(x) (0x16 + ((x) * 8)) | |
87 | #define S526_GPCT_CTRL_REG(x) (0x18 + ((x) * 8)) | |
2c6b5824 HS |
88 | #define S526_EEPROM_DATA_REG 0x32 |
89 | #define S526_EEPROM_CTRL_REG 0x34 | |
90 | #define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3) | |
91 | #define S526_EEPROM_CTRL(x) (((x) & 0x3) << 1) | |
92 | #define S526_EEPROM_CTRL_READ S526_EEPROM_CTRL(2) | |
93 | #define S526_EEPROM_CTRL_START BIT(0) | |
0c988d00 | 94 | |
4b1d53f0 | 95 | struct counter_mode_register_t { |
c9c62f4e | 96 | #if defined(__LITTLE_ENDIAN_BITFIELD) |
0c988d00 EW |
97 | unsigned short coutSource:1; |
98 | unsigned short coutPolarity:1; | |
99 | unsigned short autoLoadResetRcap:3; | |
100 | unsigned short hwCtEnableSource:2; | |
101 | unsigned short ctEnableCtrl:2; | |
102 | unsigned short clockSource:2; | |
103 | unsigned short countDir:1; | |
104 | unsigned short countDirCtrl:1; | |
105 | unsigned short outputRegLatchCtrl:1; | |
106 | unsigned short preloadRegSel:1; | |
107 | unsigned short reserved:1; | |
2b0318a6 IA |
108 | #elif defined(__BIG_ENDIAN_BITFIELD) |
109 | unsigned short reserved:1; | |
110 | unsigned short preloadRegSel:1; | |
111 | unsigned short outputRegLatchCtrl:1; | |
112 | unsigned short countDirCtrl:1; | |
113 | unsigned short countDir:1; | |
114 | unsigned short clockSource:2; | |
115 | unsigned short ctEnableCtrl:2; | |
116 | unsigned short hwCtEnableSource:2; | |
117 | unsigned short autoLoadResetRcap:3; | |
118 | unsigned short coutPolarity:1; | |
119 | unsigned short coutSource:1; | |
120 | #else | |
121 | #error Unknown bit field order | |
122 | #endif | |
4b1d53f0 | 123 | }; |
0c988d00 | 124 | |
ca98ee7b | 125 | union cmReg { |
4b1d53f0 | 126 | struct counter_mode_register_t reg; |
0c988d00 | 127 | unsigned short value; |
ca98ee7b | 128 | }; |
0c988d00 | 129 | |
6dc1ece0 | 130 | struct s526_private { |
675f98f1 | 131 | unsigned int gpct_config[4]; |
21424e3f | 132 | unsigned short ai_ctrl; |
6dc1ece0 BP |
133 | }; |
134 | ||
1d0d1c00 HS |
135 | static void s526_gpct_write(struct comedi_device *dev, |
136 | unsigned int chan, unsigned int val) | |
137 | { | |
138 | /* write high word then low word */ | |
139 | outw((val >> 16) & 0xffff, dev->iobase + S526_GPCT_MSB_REG(chan)); | |
140 | outw(val & 0xffff, dev->iobase + S526_GPCT_LSB_REG(chan)); | |
141 | } | |
142 | ||
143 | static unsigned int s526_gpct_read(struct comedi_device *dev, | |
144 | unsigned int chan) | |
145 | { | |
146 | unsigned int val; | |
147 | ||
148 | /* read the low word then high word */ | |
149 | val = inw(dev->iobase + S526_GPCT_LSB_REG(chan)) & 0xffff; | |
150 | val |= (inw(dev->iobase + S526_GPCT_MSB_REG(chan)) & 0xff) << 16; | |
151 | ||
152 | return val; | |
153 | } | |
154 | ||
0a85b6f0 | 155 | static int s526_gpct_rinsn(struct comedi_device *dev, |
2a29edf6 HS |
156 | struct comedi_subdevice *s, |
157 | struct comedi_insn *insn, | |
0a85b6f0 | 158 | unsigned int *data) |
0c988d00 | 159 | { |
43a35276 | 160 | unsigned int chan = CR_CHAN(insn->chanspec); |
43a35276 | 161 | int i; |
0c988d00 | 162 | |
1d0d1c00 HS |
163 | for (i = 0; i < insn->n; i++) |
164 | data[i] = s526_gpct_read(dev, chan); | |
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, | |
5a5614cb HS |
171 | struct comedi_insn *insn, |
172 | unsigned int *data) | |
0c988d00 | 173 | { |
5f221062 | 174 | struct s526_private *devpriv = dev->private; |
43a35276 | 175 | unsigned int chan = CR_CHAN(insn->chanspec); |
5a5614cb | 176 | unsigned int val; |
ca98ee7b | 177 | union cmReg cmReg; |
0c988d00 | 178 | |
a399d81d HS |
179 | /* |
180 | * Check what type of Counter the user requested | |
181 | * data[0] contains the Application type | |
182 | */ | |
b2abd982 | 183 | switch (data[0]) { |
0c988d00 EW |
184 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
185 | /* | |
a399d81d HS |
186 | * data[0]: Application Type |
187 | * data[1]: Counter Mode Register Value | |
188 | * data[2]: Pre-load Register Value | |
189 | * data[3]: Conter Control Register | |
0c988d00 | 190 | */ |
675f98f1 | 191 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 192 | |
0c988d00 | 193 | #if 1 |
232f6502 | 194 | /* Set Counter Mode Register */ |
5a5614cb | 195 | cmReg.value = data[1] & 0xffff; |
1d0d1c00 | 196 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 197 | |
232f6502 | 198 | /* Reset the counter if it is software preload */ |
0c988d00 | 199 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 200 | /* Reset the counter */ |
1d0d1c00 | 201 | outw(0x8000, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
c9c62f4e | 202 | /* Load the counter from PR0 |
1d0d1c00 | 203 | * outw(0x4000, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
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: */ |
a399d81d HS |
219 | /* |
220 | * if (data[2] == GPCT_IndexPhaseLowLow) { | |
221 | * } else if (data[2] == GPCT_IndexPhaseLowHigh) { | |
222 | * } else if (data[2] == GPCT_IndexPhaseHighLow) { | |
223 | * } else if (data[2] == GPCT_IndexPhaseHighHigh) { | |
224 | * } | |
225 | */ | |
232f6502 | 226 | /* Take into account the index pulse? */ |
b2abd982 | 227 | if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) |
c9c62f4e XF |
228 | /* Auto load with INDEX^ */ |
229 | cmReg.reg.autoLoadResetRcap = 4; | |
0c988d00 | 230 | |
232f6502 | 231 | /* Set Counter Mode Register */ |
5a5614cb | 232 | cmReg.value = data[1] & 0xffff; |
1d0d1c00 | 233 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 234 | |
1d0d1c00 HS |
235 | /* Load the pre-load register */ |
236 | s526_gpct_write(dev, chan, data[2]); | |
0c988d00 | 237 | |
232f6502 | 238 | /* Write the Counter Control Register */ |
1d0d1c00 HS |
239 | if (data[3]) |
240 | outw(data[3] & 0xffff, | |
241 | dev->iobase + S526_GPCT_CTRL_REG(chan)); | |
242 | ||
232f6502 | 243 | /* Reset the counter if it is software preload */ |
0c988d00 | 244 | if (cmReg.reg.autoLoadResetRcap == 0) { |
c9c62f4e | 245 | /* Reset the counter */ |
1d0d1c00 | 246 | outw(0x8000, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
c9c62f4e | 247 | /* Load the counter from PR0 */ |
1d0d1c00 | 248 | outw(0x4000, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
0c988d00 EW |
249 | } |
250 | #endif | |
251 | break; | |
252 | ||
253 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
254 | /* | |
a399d81d HS |
255 | * data[0]: Application Type |
256 | * data[1]: Counter Mode Register Value | |
257 | * data[2]: Pre-load Register 0 Value | |
258 | * data[3]: Pre-load Register 1 Value | |
259 | * data[4]: Conter Control Register | |
0c988d00 | 260 | */ |
675f98f1 | 261 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 262 | |
232f6502 | 263 | /* Set Counter Mode Register */ |
5a5614cb | 264 | cmReg.value = data[1] & 0xffff; |
232f6502 | 265 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
1d0d1c00 | 266 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 267 | |
1d0d1c00 HS |
268 | /* Load the pre-load register 0 */ |
269 | s526_gpct_write(dev, chan, data[2]); | |
0c988d00 | 270 | |
232f6502 | 271 | /* Set Counter Mode Register */ |
5a5614cb | 272 | cmReg.value = data[1] & 0xffff; |
232f6502 | 273 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
1d0d1c00 | 274 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 275 | |
1d0d1c00 HS |
276 | /* Load the pre-load register 1 */ |
277 | s526_gpct_write(dev, chan, data[3]); | |
0c988d00 | 278 | |
232f6502 | 279 | /* Write the Counter Control Register */ |
5a5614cb HS |
280 | if (data[4]) { |
281 | val = data[4] & 0xffff; | |
1d0d1c00 | 282 | outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
0c988d00 EW |
283 | } |
284 | break; | |
285 | ||
286 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
287 | /* | |
a399d81d HS |
288 | * data[0]: Application Type |
289 | * data[1]: Counter Mode Register Value | |
290 | * data[2]: Pre-load Register 0 Value | |
291 | * data[3]: Pre-load Register 1 Value | |
292 | * data[4]: Conter Control Register | |
0c988d00 | 293 | */ |
675f98f1 | 294 | devpriv->gpct_config[chan] = data[0]; |
0c988d00 | 295 | |
232f6502 | 296 | /* Set Counter Mode Register */ |
5a5614cb | 297 | cmReg.value = data[1] & 0xffff; |
232f6502 | 298 | cmReg.reg.preloadRegSel = 0; /* PR0 */ |
1d0d1c00 | 299 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 300 | |
1d0d1c00 HS |
301 | /* Load the pre-load register 0 */ |
302 | s526_gpct_write(dev, chan, data[2]); | |
0c988d00 | 303 | |
232f6502 | 304 | /* Set Counter Mode Register */ |
5a5614cb | 305 | cmReg.value = data[1] & 0xffff; |
232f6502 | 306 | cmReg.reg.preloadRegSel = 1; /* PR1 */ |
1d0d1c00 | 307 | outw(cmReg.value, dev->iobase + S526_GPCT_MODE_REG(chan)); |
0c988d00 | 308 | |
1d0d1c00 HS |
309 | /* Load the pre-load register 1 */ |
310 | s526_gpct_write(dev, chan, data[3]); | |
0c988d00 | 311 | |
232f6502 | 312 | /* Write the Counter Control Register */ |
5a5614cb HS |
313 | if (data[4]) { |
314 | val = data[4] & 0xffff; | |
1d0d1c00 | 315 | outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan)); |
0c988d00 EW |
316 | } |
317 | break; | |
318 | ||
319 | default: | |
0c988d00 | 320 | return -EINVAL; |
0c988d00 EW |
321 | } |
322 | ||
323 | return insn->n; | |
324 | } | |
325 | ||
0a85b6f0 | 326 | static int s526_gpct_winsn(struct comedi_device *dev, |
5c813bb1 HS |
327 | struct comedi_subdevice *s, |
328 | struct comedi_insn *insn, | |
0a85b6f0 | 329 | unsigned int *data) |
0c988d00 | 330 | { |
5f221062 | 331 | struct s526_private *devpriv = dev->private; |
43a35276 | 332 | unsigned int chan = CR_CHAN(insn->chanspec); |
5c813bb1 | 333 | |
1d0d1c00 | 334 | inw(dev->iobase + S526_GPCT_MODE_REG(chan)); /* Is this required? */ |
0c988d00 | 335 | |
232f6502 | 336 | /* Check what Application of Counter this channel is configured for */ |
675f98f1 HS |
337 | switch (devpriv->gpct_config[chan]) { |
338 | case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: | |
a399d81d HS |
339 | /* |
340 | * data[0] contains the PULSE_WIDTH | |
341 | * data[1] contains the PULSE_PERIOD | |
342 | * @pre PULSE_PERIOD > PULSE_WIDTH > 0 | |
343 | * The above periods must be expressed as a multiple of the | |
344 | * pulse frequency on the selected source | |
0c988d00 | 345 | */ |
67f2021f | 346 | if ((data[1] <= data[0]) || !data[0]) |
0c988d00 | 347 | return -EINVAL; |
0c988d00 | 348 | |
5c813bb1 HS |
349 | /* Fall thru to write the PULSE_WIDTH */ |
350 | ||
675f98f1 HS |
351 | case INSN_CONFIG_GPCT_QUADRATURE_ENCODER: |
352 | case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: | |
1d0d1c00 | 353 | s526_gpct_write(dev, chan, data[0]); |
0c988d00 | 354 | break; |
5c813bb1 HS |
355 | |
356 | default: | |
0c988d00 | 357 | return -EINVAL; |
0c988d00 | 358 | } |
5c813bb1 | 359 | |
0c988d00 EW |
360 | return insn->n; |
361 | } | |
362 | ||
09c5d6c8 HS |
363 | static int s526_eoc(struct comedi_device *dev, |
364 | struct comedi_subdevice *s, | |
365 | struct comedi_insn *insn, | |
366 | unsigned long context) | |
043fff87 HS |
367 | { |
368 | unsigned int status; | |
369 | ||
8a5d6d2e | 370 | status = inw(dev->iobase + S526_INT_STATUS_REG); |
09c5d6c8 HS |
371 | if (status & context) { |
372 | /* we got our eoc event, clear it */ | |
373 | outw(context, dev->iobase + S526_INT_STATUS_REG); | |
043fff87 | 374 | return 0; |
09c5d6c8 | 375 | } |
043fff87 HS |
376 | return -EBUSY; |
377 | } | |
378 | ||
bf483f1e HS |
379 | static int s526_ai_insn_read(struct comedi_device *dev, |
380 | struct comedi_subdevice *s, | |
381 | struct comedi_insn *insn, | |
382 | unsigned int *data) | |
0c988d00 | 383 | { |
5f221062 | 384 | struct s526_private *devpriv = dev->private; |
43a35276 | 385 | unsigned int chan = CR_CHAN(insn->chanspec); |
fe79b3d0 | 386 | unsigned int ctrl; |
bf483f1e | 387 | unsigned int val; |
043fff87 | 388 | int ret; |
bf483f1e | 389 | int i; |
0c988d00 | 390 | |
21424e3f | 391 | ctrl = S526_AI_CTRL_CONV(chan) | S526_AI_CTRL_READ(chan) | |
fe79b3d0 | 392 | S526_AI_CTRL_START; |
21424e3f HS |
393 | if (ctrl != devpriv->ai_ctrl) { |
394 | /* | |
395 | * The multiplexor needs to change, enable the 15us | |
396 | * delay for the first sample. | |
397 | */ | |
398 | devpriv->ai_ctrl = ctrl; | |
399 | ctrl |= S526_AI_CTRL_DELAY; | |
400 | } | |
0c988d00 | 401 | |
bf483f1e | 402 | for (i = 0; i < insn->n; i++) { |
0c988d00 | 403 | /* trigger conversion */ |
fe79b3d0 | 404 | outw(ctrl, dev->iobase + S526_AI_CTRL_REG); |
21424e3f | 405 | ctrl &= ~S526_AI_CTRL_DELAY; |
0c988d00 | 406 | |
0c988d00 | 407 | /* wait for conversion to end */ |
09c5d6c8 | 408 | ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AI); |
043fff87 HS |
409 | if (ret) |
410 | return ret; | |
411 | ||
bf483f1e HS |
412 | val = inw(dev->iobase + S526_AI_REG); |
413 | data[i] = comedi_offset_munge(s, val); | |
0c988d00 EW |
414 | } |
415 | ||
bf483f1e | 416 | return insn->n; |
0c988d00 EW |
417 | } |
418 | ||
6db4a1f5 HS |
419 | static int s526_ao_insn_write(struct comedi_device *dev, |
420 | struct comedi_subdevice *s, | |
421 | struct comedi_insn *insn, | |
422 | unsigned int *data) | |
0c988d00 | 423 | { |
43a35276 | 424 | unsigned int chan = CR_CHAN(insn->chanspec); |
abbb6489 | 425 | unsigned int ctrl = S526_AO_CTRL_CHAN(chan); |
6db4a1f5 | 426 | unsigned int val = s->readback[chan]; |
09c5d6c8 | 427 | int ret; |
43a35276 | 428 | int i; |
0c988d00 | 429 | |
abbb6489 HS |
430 | outw(ctrl, dev->iobase + S526_AO_CTRL_REG); |
431 | ctrl |= S526_AO_CTRL_START; | |
0c988d00 | 432 | |
0c988d00 | 433 | for (i = 0; i < insn->n; i++) { |
6db4a1f5 | 434 | val = data[i]; |
15bccf2e | 435 | outw(val, dev->iobase + S526_AO_REG); |
abbb6489 | 436 | outw(ctrl, dev->iobase + S526_AO_CTRL_REG); |
09c5d6c8 HS |
437 | |
438 | /* wait for conversion to end */ | |
439 | ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AO); | |
440 | if (ret) | |
441 | return ret; | |
0c988d00 | 442 | } |
6db4a1f5 | 443 | s->readback[chan] = val; |
0c988d00 | 444 | |
6db4a1f5 | 445 | return insn->n; |
0c988d00 EW |
446 | } |
447 | ||
0a85b6f0 MT |
448 | static int s526_dio_insn_bits(struct comedi_device *dev, |
449 | struct comedi_subdevice *s, | |
97f4289a HS |
450 | struct comedi_insn *insn, |
451 | unsigned int *data) | |
0c988d00 | 452 | { |
97f4289a | 453 | if (comedi_dio_update_state(s, data)) |
658441b4 | 454 | outw(s->state, dev->iobase + S526_DIO_CTRL_REG); |
0c988d00 | 455 | |
658441b4 | 456 | data[1] = inw(dev->iobase + S526_DIO_CTRL_REG) & 0xff; |
0c988d00 | 457 | |
a2714e3e | 458 | return insn->n; |
0c988d00 EW |
459 | } |
460 | ||
0a85b6f0 MT |
461 | static int s526_dio_insn_config(struct comedi_device *dev, |
462 | struct comedi_subdevice *s, | |
5dacadcc HS |
463 | struct comedi_insn *insn, |
464 | unsigned int *data) | |
0c988d00 | 465 | { |
43a35276 | 466 | unsigned int chan = CR_CHAN(insn->chanspec); |
5dacadcc HS |
467 | unsigned int mask; |
468 | int ret; | |
469 | ||
658441b4 HS |
470 | /* |
471 | * Digital I/O can be configured as inputs or outputs in | |
472 | * groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7). | |
473 | */ | |
5dacadcc HS |
474 | if (chan < 4) |
475 | mask = 0x0f; | |
476 | else | |
477 | mask = 0xf0; | |
478 | ||
479 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); | |
480 | if (ret) | |
481 | return ret; | |
482 | ||
5dacadcc | 483 | if (s->io_bits & 0x0f) |
658441b4 | 484 | s->state |= S526_DIO_CTRL_GRP1_OUT; |
5dacadcc | 485 | else |
658441b4 | 486 | s->state &= ~S526_DIO_CTRL_GRP1_OUT; |
5dacadcc | 487 | if (s->io_bits & 0xf0) |
658441b4 | 488 | s->state |= S526_DIO_CTRL_GRP2_OUT; |
5dacadcc | 489 | else |
658441b4 | 490 | s->state &= ~S526_DIO_CTRL_GRP2_OUT; |
0c988d00 | 491 | |
658441b4 | 492 | outw(s->state, dev->iobase + S526_DIO_CTRL_REG); |
0c988d00 | 493 | |
5dacadcc | 494 | return insn->n; |
0c988d00 EW |
495 | } |
496 | ||
e9a4a7fb HS |
497 | static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
498 | { | |
5f221062 | 499 | struct s526_private *devpriv; |
e9a4a7fb | 500 | struct comedi_subdevice *s; |
8b6c5694 | 501 | int ret; |
e9a4a7fb | 502 | |
862755ec | 503 | ret = comedi_request_region(dev, it->options[0], 0x40); |
3cbc2810 HS |
504 | if (ret) |
505 | return ret; | |
e9a4a7fb | 506 | |
0bdab509 | 507 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
508 | if (!devpriv) |
509 | return -ENOMEM; | |
e9a4a7fb | 510 | |
8b6c5694 HS |
511 | ret = comedi_alloc_subdevices(dev, 4); |
512 | if (ret) | |
513 | return ret; | |
e9a4a7fb | 514 | |
12911c2d | 515 | /* General-Purpose Counter/Timer (GPCT) */ |
97073c05 | 516 | s = &dev->subdevices[0]; |
12911c2d HS |
517 | s->type = COMEDI_SUBD_COUNTER; |
518 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; | |
519 | s->n_chan = 4; | |
520 | s->maxdata = 0x00ffffff; | |
521 | s->insn_read = s526_gpct_rinsn; | |
522 | s->insn_config = s526_gpct_insn_config; | |
523 | s->insn_write = s526_gpct_winsn; | |
e9a4a7fb | 524 | |
12911c2d HS |
525 | /* |
526 | * Analog Input subdevice | |
527 | * channels 0 to 7 are the regular differential inputs | |
528 | * channel 8 is "reference 0" (+10V) | |
529 | * channel 9 is "reference 1" (0V) | |
530 | */ | |
97073c05 | 531 | s = &dev->subdevices[1]; |
12911c2d HS |
532 | s->type = COMEDI_SUBD_AI; |
533 | s->subdev_flags = SDF_READABLE | SDF_DIFF; | |
534 | s->n_chan = 10; | |
535 | s->maxdata = 0xffff; | |
536 | s->range_table = &range_bipolar10; | |
537 | s->len_chanlist = 16; | |
bf483f1e | 538 | s->insn_read = s526_ai_insn_read; |
12911c2d HS |
539 | |
540 | /* Analog Output subdevice */ | |
97073c05 | 541 | s = &dev->subdevices[2]; |
12911c2d HS |
542 | s->type = COMEDI_SUBD_AO; |
543 | s->subdev_flags = SDF_WRITABLE; | |
544 | s->n_chan = 4; | |
545 | s->maxdata = 0xffff; | |
546 | s->range_table = &range_bipolar10; | |
547 | s->insn_write = s526_ao_insn_write; | |
6db4a1f5 HS |
548 | |
549 | ret = comedi_alloc_subdev_readback(s); | |
550 | if (ret) | |
551 | return ret; | |
e9a4a7fb | 552 | |
12911c2d | 553 | /* Digital I/O subdevice */ |
97073c05 | 554 | s = &dev->subdevices[3]; |
12911c2d HS |
555 | s->type = COMEDI_SUBD_DIO; |
556 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
557 | s->n_chan = 8; | |
558 | s->maxdata = 1; | |
559 | s->range_table = &range_digital; | |
560 | s->insn_bits = s526_dio_insn_bits; | |
561 | s->insn_config = s526_dio_insn_config; | |
e9a4a7fb | 562 | |
fb780d21 | 563 | return 0; |
e9a4a7fb HS |
564 | } |
565 | ||
294f930d | 566 | static struct comedi_driver s526_driver = { |
e9a4a7fb HS |
567 | .driver_name = "s526", |
568 | .module = THIS_MODULE, | |
569 | .attach = s526_attach, | |
21208519 | 570 | .detach = comedi_legacy_detach, |
e9a4a7fb | 571 | }; |
294f930d | 572 | module_comedi_driver(s526_driver); |
90f703d3 AT |
573 | |
574 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
575 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
576 | MODULE_LICENSE("GPL"); |