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 | |
25985edc LDM |
42 | [1] - conversion rate |
43 | Conversion rate RMS noise Effective Number Of Bits | |
b8c623e5 IC |
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 | ||
25985edc LDM |
96 | /* Conversion speeds */ |
97 | /* OSR4 OSR3 OSR2 OSR1 OSR0 Conversion rate RMS noise ENOB^ | |
90a5038d SR |
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 | ||
79a31bae | 151 | /* Timeout 200ms */ |
90a5038d SR |
152 | #define TIMEOUT 200 |
153 | ||
0a85b6f0 MT |
154 | static int mpc624_ai_rinsn(struct comedi_device *dev, |
155 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
156 | unsigned int *data) | |
90a5038d SR |
157 | { |
158 | int n, i; | |
159 | unsigned long int data_in, data_out; | |
160 | unsigned char ucPort; | |
161 | ||
b8c623e5 IC |
162 | /* |
163 | * WARNING: | |
164 | * We always write 0 to GNSWA bit, so the channel range is +-/10.1Vdc | |
165 | */ | |
90a5038d | 166 | outb(insn->chanspec, dev->iobase + MPC624_GNMUXCH); |
b8c623e5 | 167 | /* printk("Channel %d:\n", insn->chanspec); */ |
90a5038d | 168 | if (!insn->n) { |
b8c623e5 | 169 | printk(KERN_INFO "MPC624: Warning, no data to acquire\n"); |
90a5038d SR |
170 | return 0; |
171 | } | |
172 | ||
173 | for (n = 0; n < insn->n; n++) { | |
25985edc | 174 | /* Trigger the conversion */ |
90a5038d | 175 | outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); |
5f74ea14 | 176 | udelay(1); |
90a5038d | 177 | outb(MPC624_ADCS | MPC624_ADSCK, dev->iobase + MPC624_ADC); |
5f74ea14 | 178 | udelay(1); |
90a5038d | 179 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 180 | udelay(1); |
90a5038d | 181 | |
25985edc | 182 | /* Wait for the conversion to end */ |
90a5038d SR |
183 | for (i = 0; i < TIMEOUT; i++) { |
184 | ucPort = inb(dev->iobase + MPC624_ADC); | |
185 | if (ucPort & MPC624_ADBUSY) | |
5f74ea14 | 186 | udelay(1000); |
90a5038d SR |
187 | else |
188 | break; | |
189 | } | |
190 | if (i == TIMEOUT) { | |
b8c623e5 | 191 | printk(KERN_ERR "MPC624: timeout (%dms)\n", TIMEOUT); |
90a5038d SR |
192 | data[n] = 0; |
193 | return -ETIMEDOUT; | |
194 | } | |
79a31bae | 195 | /* Start reading data */ |
90a5038d SR |
196 | data_in = 0; |
197 | data_out = devpriv->ulConvertionRate; | |
5f74ea14 | 198 | udelay(1); |
90a5038d | 199 | for (i = 0; i < 32; i++) { |
79a31bae | 200 | /* Set the clock low */ |
90a5038d | 201 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 202 | udelay(1); |
90a5038d | 203 | |
b8c623e5 | 204 | if (data_out & (1 << 31)) { /* the next bit is a 1 */ |
79a31bae | 205 | /* Set the ADSDI line (send to MPC624) */ |
90a5038d | 206 | outb(MPC624_ADSDI, dev->iobase + MPC624_ADC); |
5f74ea14 | 207 | udelay(1); |
79a31bae | 208 | /* Set the clock high */ |
90a5038d | 209 | outb(MPC624_ADSCK | MPC624_ADSDI, |
0a85b6f0 MT |
210 | dev->iobase + MPC624_ADC); |
211 | } else { /* the next bit is a 0 */ | |
212 | ||
79a31bae | 213 | /* Set the ADSDI line (send to MPC624) */ |
90a5038d | 214 | outb(0, dev->iobase + MPC624_ADC); |
5f74ea14 | 215 | udelay(1); |
79a31bae | 216 | /* Set the clock high */ |
90a5038d SR |
217 | outb(MPC624_ADSCK, dev->iobase + MPC624_ADC); |
218 | } | |
79a31bae | 219 | /* Read ADSDO on high clock (receive from MPC624) */ |
5f74ea14 | 220 | udelay(1); |
90a5038d SR |
221 | data_in <<= 1; |
222 | data_in |= | |
0a85b6f0 | 223 | (inb(dev->iobase + MPC624_ADC) & MPC624_ADSDO) >> 4; |
5f74ea14 | 224 | udelay(1); |
90a5038d SR |
225 | |
226 | data_out <<= 1; | |
227 | } | |
228 | ||
b8c623e5 IC |
229 | /* |
230 | * Received 32-bit long value consist of: | |
231 | * 31: EOC - | |
232 | * (End Of Transmission) bit - should be 0 | |
233 | * 30: DMY | |
234 | * (Dummy) bit - should be 0 | |
235 | * 29: SIG | |
236 | * (Sign) bit- 1 if the voltage is positive, | |
237 | * 0 if negative | |
238 | * 28: MSB | |
239 | * (Most Significant Bit) - the first bit of | |
240 | * the conversion result | |
241 | * .... | |
242 | * 05: LSB | |
243 | * (Least Significant Bit)- the last bit of the | |
244 | * conversion result | |
245 | * 04-00: sub-LSB | |
246 | * - sub-LSBs are basically noise, but when | |
247 | * averaged properly, they can increase conversion | |
248 | * precision up to 29 bits; they can be discarded | |
249 | * without loss of resolution. | |
250 | */ | |
90a5038d SR |
251 | |
252 | if (data_in & MPC624_EOC_BIT) | |
b8c623e5 | 253 | printk(KERN_INFO "MPC624:EOC bit is set (data_in=%lu)!", |
0a85b6f0 | 254 | data_in); |
90a5038d | 255 | if (data_in & MPC624_DMY_BIT) |
b8c623e5 | 256 | printk(KERN_INFO "MPC624:DMY bit is set (data_in=%lu)!", |
0a85b6f0 | 257 | data_in); |
b8c623e5 IC |
258 | if (data_in & MPC624_SGN_BIT) { /* Volatge is positive */ |
259 | /* | |
260 | * comedi operates on unsigned numbers, so mask off EOC | |
261 | * and DMY and don't clear the SGN bit | |
262 | */ | |
263 | data_in &= 0x3FFFFFFF; | |
264 | data[n] = data_in; | |
265 | } else { /* The voltage is negative */ | |
266 | /* | |
267 | * data_in contains a number in 30-bit two's complement | |
268 | * code and we must deal with it | |
269 | */ | |
90a5038d SR |
270 | data_in |= MPC624_SGN_BIT; |
271 | data_in = ~data_in; | |
272 | data_in += 1; | |
273 | data_in &= ~(MPC624_EOC_BIT | MPC624_DMY_BIT); | |
79a31bae | 274 | /* clear EOC and DMY bits */ |
90a5038d SR |
275 | data_in = 0x20000000 - data_in; |
276 | data[n] = data_in; | |
277 | } | |
278 | } | |
279 | ||
79a31bae | 280 | /* Return the number of samples read/written */ |
90a5038d SR |
281 | return n; |
282 | } | |
283 | ||
9142e00e | 284 | static int mpc624_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
7114a280 | 285 | { |
9142e00e HS |
286 | struct comedi_subdevice *s; |
287 | unsigned long iobase; | |
8b6c5694 | 288 | int ret; |
9142e00e HS |
289 | |
290 | iobase = it->options[0]; | |
291 | printk(KERN_INFO "comedi%d: mpc624 [0x%04lx, ", dev->minor, iobase); | |
292 | if (request_region(iobase, MPC624_SIZE, "mpc624") == NULL) { | |
293 | printk(KERN_ERR "I/O port(s) in use\n"); | |
294 | return -EIO; | |
295 | } | |
296 | ||
297 | dev->iobase = iobase; | |
298 | dev->board_name = "mpc624"; | |
299 | ||
300 | /* Private structure initialization */ | |
301 | if (alloc_private(dev, sizeof(struct skel_private)) < 0) | |
302 | return -ENOMEM; | |
303 | ||
304 | switch (it->options[1]) { | |
305 | case 0: | |
306 | devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; | |
307 | printk(KERN_INFO "3.52 kHz, "); | |
308 | break; | |
309 | case 1: | |
310 | devpriv->ulConvertionRate = MPC624_SPEED_1_76_kHz; | |
311 | printk(KERN_INFO "1.76 kHz, "); | |
312 | break; | |
313 | case 2: | |
314 | devpriv->ulConvertionRate = MPC624_SPEED_880_Hz; | |
315 | printk(KERN_INFO "880 Hz, "); | |
316 | break; | |
317 | case 3: | |
318 | devpriv->ulConvertionRate = MPC624_SPEED_440_Hz; | |
319 | printk(KERN_INFO "440 Hz, "); | |
320 | break; | |
321 | case 4: | |
322 | devpriv->ulConvertionRate = MPC624_SPEED_220_Hz; | |
323 | printk(KERN_INFO "220 Hz, "); | |
324 | break; | |
325 | case 5: | |
326 | devpriv->ulConvertionRate = MPC624_SPEED_110_Hz; | |
327 | printk(KERN_INFO "110 Hz, "); | |
328 | break; | |
329 | case 6: | |
330 | devpriv->ulConvertionRate = MPC624_SPEED_55_Hz; | |
331 | printk(KERN_INFO "55 Hz, "); | |
332 | break; | |
333 | case 7: | |
334 | devpriv->ulConvertionRate = MPC624_SPEED_27_5_Hz; | |
335 | printk(KERN_INFO "27.5 Hz, "); | |
336 | break; | |
337 | case 8: | |
338 | devpriv->ulConvertionRate = MPC624_SPEED_13_75_Hz; | |
339 | printk(KERN_INFO "13.75 Hz, "); | |
340 | break; | |
341 | case 9: | |
342 | devpriv->ulConvertionRate = MPC624_SPEED_6_875_Hz; | |
343 | printk(KERN_INFO "6.875 Hz, "); | |
344 | break; | |
345 | default: | |
346 | printk | |
347 | (KERN_ERR "illegal conversion rate setting!" | |
348 | " Valid numbers are 0..9. Using 9 => 6.875 Hz, "); | |
349 | devpriv->ulConvertionRate = MPC624_SPEED_3_52_kHz; | |
350 | } | |
351 | ||
8b6c5694 HS |
352 | ret = comedi_alloc_subdevices(dev, 1); |
353 | if (ret) | |
354 | return ret; | |
9142e00e | 355 | |
283fd0b2 | 356 | s = &dev->subdevices[0]; |
9142e00e HS |
357 | s->type = COMEDI_SUBD_AI; |
358 | s->subdev_flags = SDF_READABLE | SDF_DIFF; | |
359 | s->n_chan = 8; | |
360 | switch (it->options[1]) { | |
361 | default: | |
362 | s->maxdata = 0x3FFFFFFF; | |
363 | printk(KERN_INFO "30 bit, "); | |
364 | } | |
365 | ||
366 | switch (it->options[1]) { | |
367 | case 0: | |
368 | s->range_table = &range_mpc624_bipolar1; | |
369 | printk(KERN_INFO "1.01V]: "); | |
370 | break; | |
371 | default: | |
372 | s->range_table = &range_mpc624_bipolar10; | |
373 | printk(KERN_INFO "10.1V]: "); | |
374 | } | |
375 | s->len_chanlist = 1; | |
376 | s->insn_read = mpc624_ai_rinsn; | |
377 | ||
378 | printk(KERN_INFO "attached\n"); | |
379 | ||
380 | return 1; | |
7114a280 AT |
381 | } |
382 | ||
484ecc95 | 383 | static void mpc624_detach(struct comedi_device *dev) |
7114a280 | 384 | { |
9142e00e HS |
385 | if (dev->iobase) |
386 | release_region(dev->iobase, MPC624_SIZE); | |
7114a280 AT |
387 | } |
388 | ||
9142e00e HS |
389 | static struct comedi_driver mpc624_driver = { |
390 | .driver_name = "mpc624", | |
391 | .module = THIS_MODULE, | |
392 | .attach = mpc624_attach, | |
393 | .detach = mpc624_detach | |
394 | }; | |
395 | module_comedi_driver(mpc624_driver); | |
90f703d3 AT |
396 | |
397 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
398 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
399 | MODULE_LICENSE("GPL"); |