Commit | Line | Data |
---|---|---|
90a5038d SR |
1 | /* |
2 | comedi/drivers/mpc624.c | |
3 | Hardware driver for a Micro/sys inc. MPC-624 PC/104 board | |
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: mpc624 | |
25 | Description: Micro/sys MPC-624 PC/104 board | |
26 | Devices: [Micro/sys] MPC-624 (mpc624) | |
27 | Author: Stanislaw Raczynski <sraczynski@op.pl> | |
28 | Updated: Thu, 15 Sep 2005 12:01:18 +0200 | |
29 | Status: working | |
30 | ||
31 | The Micro/sys MPC-624 board is based on the LTC2440 24-bit sigma-delta | |
32 | ADC chip. | |
33 | ||
34 | Subdevices supported by the driver: | |
35 | - Analog In: supported | |
36 | - Digital I/O: not supported | |
37 | - LEDs: not supported | |
38 | - EEPROM: not supported | |
39 | ||
40 | Configuration Options: | |
41 | [0] - I/O base address | |
42 | [1] - convertion rate | |
b8c623e5 IC |
43 | Convertion rate RMS noise Effective Number Of Bits |
44 | 0 3.52kHz 23uV 17 | |
45 | 1 1.76kHz 3.5uV 20 | |
46 | 2 880Hz 2uV 21.3 | |
47 | 3 440Hz 1.4uV 21.8 | |
48 | 4 220Hz 1uV 22.4 | |
49 | 5 110Hz 750uV 22.9 | |
50 | 6 55Hz 510nV 23.4 | |
51 | 7 27.5Hz 375nV 24 | |
52 | 8 13.75Hz 250nV 24.4 | |
53 | 9 6.875Hz 200nV 24.6 | |
54 | [2] - voltage range | |
55 | 0 -1.01V .. +1.01V | |
56 | 1 -10.1V .. +10.1V | |
90a5038d SR |
57 | */ |
58 | ||
59 | #include "../comedidev.h" | |
60 | ||
61 | #include <linux/ioport.h> | |
62 | #include <linux/delay.h> | |
63 | ||
79a31bae | 64 | /* Consecutive I/O port addresses */ |
90a5038d SR |
65 | #define MPC624_SIZE 16 |
66 | ||
79a31bae | 67 | /* Offsets of different ports */ |
b8c623e5 IC |
68 | #define MPC624_MASTER_CONTROL 0 /* not used */ |
69 | #define MPC624_GNMUXCH 1 /* Gain, Mux, Channel of ADC */ | |
70 | #define MPC624_ADC 2 /* read/write to/from ADC */ | |
71 | #define MPC624_EE 3 /* read/write to/from serial EEPROM via I2C */ | |
72 | #define MPC624_LEDS 4 /* write to LEDs */ | |
73 | #define MPC624_DIO 5 /* read/write to/from digital I/O ports */ | |
74 | #define MPC624_IRQ_MASK 6 /* IRQ masking enable/disable */ | |
90a5038d | 75 | |
79a31bae | 76 | /* Register bits' names */ |
90a5038d SR |
77 | #define MPC624_ADBUSY (1<<5) |
78 | #define MPC624_ADSDO (1<<4) | |
79 | #define MPC624_ADFO (1<<3) | |
80 | #define MPC624_ADCS (1<<2) | |
81 | #define MPC624_ADSCK (1<<1) | |
82 | #define MPC624_ADSDI (1<<0) | |
83 | ||
79a31bae | 84 | /* SDI Speed/Resolution Programming bits */ |
90a5038d SR |
85 | #define MPC624_OSR4 (1<<31) |
86 | #define MPC624_OSR3 (1<<30) | |
87 | #define MPC624_OSR2 (1<<29) | |
88 | #define MPC624_OSR1 (1<<28) | |
89 | #define MPC624_OSR0 (1<<27) | |
90 | ||
79a31bae | 91 | /* 32-bit output value bits' names */ |
90a5038d SR |
92 | #define MPC624_EOC_BIT (1<<31) |
93 | #define MPC624_DMY_BIT (1<<30) | |
94 | #define MPC624_SGN_BIT (1<<29) | |
95 | ||
79a31bae | 96 | /* Convertion speeds */ |
90a5038d SR |
97 | /* OSR4 OSR3 OSR2 OSR1 OSR0 Convertion rate RMS noise ENOB^ |
98 | * X 0 0 0 1 3.52kHz 23uV 17 | |
99 | * X 0 0 1 0 1.76kHz 3.5uV 20 | |
100 | * X 0 0 1 1 880Hz 2uV 21.3 | |
101 | * X 0 1 0 0 440Hz 1.4uV 21.8 | |
102 | * X 0 1 0 1 220Hz 1uV 22.4 | |
103 | * X 0 1 1 0 110Hz 750uV 22.9 | |
104 | * X 0 1 1 1 55Hz 510nV 23.4 | |
105 | * X 1 0 0 0 27.5Hz 375nV 24 | |
106 | * X 1 0 0 1 13.75Hz 250nV 24.4 | |
107 | * X 1 1 1 1 6.875Hz 200nV 24.6 | |
108 | * | |
109 | * ^ - Effective Number Of Bits | |
110 | */ | |
111 | ||
b8c623e5 IC |
112 | #define MPC624_SPEED_3_52_kHz (MPC624_OSR4 | MPC624_OSR0) |
113 | #define MPC624_SPEED_1_76_kHz (MPC624_OSR4 | MPC624_OSR1) | |
114 | #define MPC624_SPEED_880_Hz (MPC624_OSR4 | MPC624_OSR1 | MPC624_OSR0) | |
115 | #define MPC624_SPEED_440_Hz (MPC624_OSR4 | MPC624_OSR2) | |
116 | #define MPC624_SPEED_220_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR0) | |
117 | #define MPC624_SPEED_110_Hz (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1) | |
118 | #define MPC624_SPEED_55_Hz \ | |
119 | (MPC624_OSR4 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0) | |
120 | #define MPC624_SPEED_27_5_Hz (MPC624_OSR4 | MPC624_OSR3) | |
121 | #define MPC624_SPEED_13_75_Hz (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR0) | |
122 | #define MPC624_SPEED_6_875_Hz \ | |
123 | (MPC624_OSR4 | MPC624_OSR3 | MPC624_OSR2 | MPC624_OSR1 | MPC624_OSR0) | |
124 | /* -------------------------------------------------------------------------- */ | |
16450130 BP |
125 | struct skel_private { |
126 | ||
b8c623e5 IC |
127 | /* set by mpc624_attach() from driver's parameters */ |
128 | unsigned long int ulConvertionRate; | |
16450130 BP |
129 | }; |
130 | ||
16450130 | 131 | #define devpriv ((struct skel_private *)dev->private) |
b8c623e5 | 132 | /* -------------------------------------------------------------------------- */ |
9ced1de6 | 133 | static const struct comedi_lrange range_mpc624_bipolar1 = { |
90a5038d SR |
134 | 1, |
135 | { | |
79a31bae | 136 | /* BIP_RANGE(1.01) this is correct, */ |
0a85b6f0 MT |
137 | /* but my MPC-624 actually seems to have a range of 2.02 */ |
138 | BIP_RANGE(2.02) | |
139 | } | |
90a5038d | 140 | }; |
0a85b6f0 | 141 | |
9ced1de6 | 142 | static const struct comedi_lrange range_mpc624_bipolar10 = { |
90a5038d SR |
143 | 1, |
144 | { | |
79a31bae | 145 | /* BIP_RANGE(10.1) this is correct, */ |
0a85b6f0 MT |
146 | /* but my MPC-624 actually seems to have a range of 20.2 */ |
147 | BIP_RANGE(20.2) | |
148 | } | |
90a5038d SR |
149 | }; |
150 | ||
b8c623e5 | 151 | /* -------------------------------------------------------------------------- */ |
0a85b6f0 MT |
152 | static int mpc624_attach(struct comedi_device *dev, |
153 | struct comedi_devconfig *it); | |
da91b269 | 154 | static int mpc624_detach(struct comedi_device *dev); |
b8c623e5 | 155 | /* -------------------------------------------------------------------------- */ |
139dfbdf | 156 | static struct comedi_driver driver_mpc624 = { |
68c3dbff BP |
157 | .driver_name = "mpc624", |
158 | .module = THIS_MODULE, | |
159 | .attach = mpc624_attach, | |
160 | .detach = mpc624_detach | |
90a5038d SR |
161 | }; |
162 | ||
b8c623e5 | 163 | /* -------------------------------------------------------------------------- */ |
0a85b6f0 MT |
164 | static int mpc624_ai_rinsn(struct comedi_device *dev, |
165 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
166 | unsigned int *data); | |
b8c623e5 | 167 | /* -------------------------------------------------------------------------- */ |
da91b269 | 168 | static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
90a5038d | 169 | { |
34c43922 | 170 | struct comedi_subdevice *s; |
90a5038d SR |
171 | unsigned long iobase; |
172 | ||
173 | iobase = it->options[0]; | |
b8c623e5 | 174 | printk(KERN_INFO "comedi%d: mpc624 [0x%04lx, ", dev->minor, iobase); |
90a5038d | 175 | if (request_region(iobase, MPC624_SIZE, "mpc624") == NULL) { |
b8c623e5 | 176 | printk(KERN_ERR "I/O port(s) in use\n"); |
90a5038d SR |
177 | return -EIO; |
178 | } | |
179 | ||
180 | dev->iobase = iobase; | |
181 | dev->board_name = "mpc624"; | |
182 | ||
79a31bae | 183 | /* Private structure initialization */ |
16450130 | 184 | if (alloc_private(dev, sizeof(struct skel_private)) < 0) |
90a5038d SR |
185 | return -ENOMEM; |
186 | ||
187 | switch (it->options[1]) { | |
188 | case 0: | |
189 | devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; | |
b8c623e5 | 190 | printk(KERN_INFO "3.52 kHz, "); |
90a5038d SR |
191 | break; |
192 | case 1: | |
193 | devpriv->ulConvertionRate = MPC624_SPEED_1_76_kHz; | |
b8c623e5 | 194 | printk(KERN_INFO "1.76 kHz, "); |
90a5038d SR |
195 | break; |
196 | case 2: | |
197 | devpriv->ulConvertionRate = MPC624_SPEED_880_Hz; | |
b8c623e5 | 198 | printk(KERN_INFO "880 Hz, "); |
90a5038d SR |
199 | break; |
200 | case 3: | |
201 | devpriv->ulConvertionRate = MPC624_SPEED_440_Hz; | |
b8c623e5 | 202 | printk(KERN_INFO "440 Hz, "); |
90a5038d SR |
203 | break; |
204 | case 4: | |
205 | devpriv->ulConvertionRate = MPC624_SPEED_220_Hz; | |
b8c623e5 | 206 | printk(KERN_INFO "220 Hz, "); |
90a5038d SR |
207 | break; |
208 | case 5: | |
209 | devpriv->ulConvertionRate = MPC624_SPEED_110_Hz; | |
b8c623e5 | 210 | printk(KERN_INFO "110 Hz, "); |
90a5038d SR |
211 | break; |
212 | case 6: | |
213 | devpriv->ulConvertionRate = MPC624_SPEED_55_Hz; | |
b8c623e5 | 214 | printk(KERN_INFO "55 Hz, "); |
90a5038d SR |
215 | break; |
216 | case 7: | |
217 | devpriv->ulConvertionRate = MPC624_SPEED_27_5_Hz; | |
b8c623e5 | 218 | printk(KERN_INFO "27.5 Hz, "); |
90a5038d SR |
219 | break; |
220 | case 8: | |
221 | devpriv->ulConvertionRate = MPC624_SPEED_13_75_Hz; | |
b8c623e5 | 222 | printk(KERN_INFO "13.75 Hz, "); |
90a5038d SR |
223 | break; |
224 | case 9: | |
225 | devpriv->ulConvertionRate = MPC624_SPEED_6_875_Hz; | |
b8c623e5 | 226 | printk(KERN_INFO "6.875 Hz, "); |
90a5038d SR |
227 | break; |
228 | default: | |
5f74ea14 | 229 | printk |
b8c623e5 IC |
230 | (KERN_ERR "illegal convertion rate setting!" |
231 | " Valid numbers are 0..9. Using 9 => 6.875 Hz, "); | |
90a5038d SR |
232 | devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; |
233 | } | |
234 | ||
79a31bae | 235 | /* Subdevices structures */ |
90a5038d SR |
236 | if (alloc_subdevices(dev, 1) < 0) |
237 | return -ENOMEM; | |
238 | ||
239 | s = dev->subdevices + 0; | |
240 | s->type = COMEDI_SUBD_AI; | |
241 | s->subdev_flags = SDF_READABLE | SDF_DIFF; | |
242 | s->n_chan = 8; | |
243 | switch (it->options[1]) { | |
244 | default: | |
245 | s->maxdata = 0x3FFFFFFF; | |
b8c623e5 | 246 | printk(KERN_INFO "30 bit, "); |
90a5038d SR |
247 | } |
248 | ||
249 | switch (it->options[1]) { | |
250 | case 0: | |
251 | s->range_table = &range_mpc624_bipolar1; | |
b8c623e5 | 252 | printk(KERN_INFO "1.01V]: "); |
90a5038d SR |
253 | break; |
254 | default: | |
255 | s->range_table = &range_mpc624_bipolar10; | |
b8c623e5 | 256 | printk(KERN_INFO "10.1V]: "); |
90a5038d SR |
257 | } |
258 | s->len_chanlist = 1; | |
259 | s->insn_read = mpc624_ai_rinsn; | |
260 | ||
b8c623e5 | 261 | printk(KERN_INFO "attached\n"); |
90a5038d SR |
262 | |
263 | return 1; | |
264 | } | |
265 | ||
da91b269 | 266 | static int mpc624_detach(struct comedi_device *dev) |
90a5038d | 267 | { |
b8c623e5 | 268 | printk(KERN_INFO "comedi%d: mpc624: remove\n", dev->minor); |
90a5038d SR |
269 | |
270 | if (dev->iobase) | |
271 | release_region(dev->iobase, MPC624_SIZE); | |
272 | ||
273 | return 0; | |
274 | } | |
275 | ||
79a31bae | 276 | /* Timeout 200ms */ |
90a5038d SR |
277 | #define TIMEOUT 200 |
278 | ||
0a85b6f0 MT |
279 | static int mpc624_ai_rinsn(struct comedi_device *dev, |
280 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
281 | unsigned int *data) | |
90a5038d SR |
282 | { |
283 | int n, i; | |
284 | unsigned long int data_in, data_out; | |
285 | unsigned char ucPort; | |
286 | ||
b8c623e5 IC |
287 | /* |
288 | * WARNING: | |
289 | * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc | |
290 | */ | |
90a5038d | 291 | outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH); |
b8c623e5 | 292 | /* printk("Channel %d:\n", insn->chanspec); */ |
90a5038d | 293 | if (!insn->n) { |
b8c623e5 | 294 | printk(KERN_INFO "MPC624: Warning, no data to acquire\n"); |
90a5038d SR |
295 | return 0; |
296 | } | |
297 | ||
298 | for (n = 0; n < insn->n; n++) { | |
79a31bae | 299 | /* Trigger the convertion */ |
90a5038d | 300 | outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); |
5f74ea14 | 301 | udelay(1); |
90a5038d | 302 | outb(MPC624_ADCS | MPC624_ADSCK, dev->iobase + MPC624_ADC); |
5f74ea14 | 303 | udelay(1); |
90a5038d | 304 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 305 | udelay(1); |
90a5038d | 306 | |
79a31bae | 307 | /* Wait for the convertion to end */ |
90a5038d SR |
308 | for (i = 0; i < TIMEOUT; i++) { |
309 | ucPort = inb(dev->iobase + MPC624_ADC); | |
310 | if (ucPort & MPC624_ADBUSY) | |
5f74ea14 | 311 | udelay(1000); |
90a5038d SR |
312 | else |
313 | break; | |
314 | } | |
315 | if (i == TIMEOUT) { | |
b8c623e5 | 316 | printk(KERN_ERR "MPC624: timeout (%dms)\n", TIMEOUT); |
90a5038d SR |
317 | data[n] = 0; |
318 | return -ETIMEDOUT; | |
319 | } | |
79a31bae | 320 | /* Start reading data */ |
90a5038d SR |
321 | data_in = 0; |
322 | data_out = devpriv->ulConvertionRate; | |
5f74ea14 | 323 | udelay(1); |
90a5038d | 324 | for (i = 0; i < 32; i++) { |
79a31bae | 325 | /* Set the clock low */ |
90a5038d | 326 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 327 | udelay(1); |
90a5038d | 328 | |
b8c623e5 | 329 | if (data_out & (1 << 31)) { /* the next bit is a 1 */ |
79a31bae | 330 | /* Set the ADSDI line (send to MPC624) */ |
90a5038d | 331 | outb(MPC624_ADSDI, dev->iobase + MPC624_ADC); |
5f74ea14 | 332 | udelay(1); |
79a31bae | 333 | /* Set the clock high */ |
90a5038d | 334 | outb(MPC624_ADSCK | MPC624_ADSDI, |
0a85b6f0 MT |
335 | dev->iobase + MPC624_ADC); |
336 | } else { /* the next bit is a 0 */ | |
337 | ||
79a31bae | 338 | /* Set the ADSDI line (send to MPC624) */ |
90a5038d | 339 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 340 | udelay(1); |
79a31bae | 341 | /* Set the clock high */ |
90a5038d SR |
342 | outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); |
343 | } | |
79a31bae | 344 | /* Read ADSDO on high clock (receive from MPC624) */ |
5f74ea14 | 345 | udelay(1); |
90a5038d SR |
346 | data_in <<= 1; |
347 | data_in |= | |
0a85b6f0 | 348 | (inb(dev->iobase + MPC624_ADC) & MPC624_ADSDO) >> 4; |
5f74ea14 | 349 | udelay(1); |
90a5038d SR |
350 | |
351 | data_out <<= 1; | |
352 | } | |
353 | ||
b8c623e5 IC |
354 | /* |
355 | * Received 32-bit long value consist of: | |
356 | * 31: EOC - | |
357 | * (End Of Transmission) bit - should be 0 | |
358 | * 30: DMY | |
359 | * (Dummy) bit - should be 0 | |
360 | * 29: SIG | |
361 | * (Sign) bit- 1 if the voltage is positive, | |
362 | * 0 if negative | |
363 | * 28: MSB | |
364 | * (Most Significant Bit) - the first bit of | |
365 | * the conversion result | |
366 | * .... | |
367 | * 05: LSB | |
368 | * (Least Significant Bit)- the last bit of the | |
369 | * conversion result | |
370 | * 04-00: sub-LSB | |
371 | * - sub-LSBs are basically noise, but when | |
372 | * averaged properly, they can increase conversion | |
373 | * precision up to 29 bits; they can be discarded | |
374 | * without loss of resolution. | |
375 | */ | |
90a5038d SR |
376 | |
377 | if (data_in & MPC624_EOC_BIT) | |
b8c623e5 | 378 | printk(KERN_INFO "MPC624:EOC bit is set (data_in=%lu)!", |
0a85b6f0 | 379 | data_in); |
90a5038d | 380 | if (data_in & MPC624_DMY_BIT) |
b8c623e5 | 381 | printk(KERN_INFO "MPC624:DMY bit is set (data_in=%lu)!", |
0a85b6f0 | 382 | data_in); |
b8c623e5 IC |
383 | if (data_in & MPC624_SGN_BIT) { /* Volatge is positive */ |
384 | /* | |
385 | * comedi operates on unsigned numbers, so mask off EOC | |
386 | * and DMY and don't clear the SGN bit | |
387 | */ | |
388 | data_in &= 0x3FFFFFFF; | |
389 | data[n] = data_in; | |
390 | } else { /* The voltage is negative */ | |
391 | /* | |
392 | * data_in contains a number in 30-bit two's complement | |
393 | * code and we must deal with it | |
394 | */ | |
90a5038d SR |
395 | data_in |= MPC624_SGN_BIT; |
396 | data_in = ~data_in; | |
397 | data_in += 1; | |
398 | data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT); | |
79a31bae | 399 | /* clear EOC and DMY bits */ |
90a5038d SR |
400 | data_in = 0x20000000 - data_in; |
401 | data[n] = data_in; | |
402 | } | |
403 | } | |
404 | ||
79a31bae | 405 | /* Return the number of samples read/written */ |
90a5038d SR |
406 | return n; |
407 | } | |
408 | ||
7114a280 AT |
409 | static int __init driver_mpc624_init_module(void) |
410 | { | |
411 | return comedi_driver_register(&driver_mpc624); | |
412 | } | |
413 | ||
414 | static void __exit driver_mpc624_cleanup_module(void) | |
415 | { | |
416 | comedi_driver_unregister(&driver_mpc624); | |
417 | } | |
418 | ||
419 | module_init(driver_mpc624_init_module); | |
420 | module_exit(driver_mpc624_cleanup_module); | |
90f703d3 AT |
421 | |
422 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
423 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
424 | MODULE_LICENSE("GPL"); |