Commit | Line | Data |
---|---|---|
a211ea97 DS |
1 | /* |
2 | comedi/drivers/dt2814.c | |
3 | Hardware driver for Data Translation DT2814 | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 1998 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: dt2814 | |
25 | Description: Data Translation DT2814 | |
26 | Author: ds | |
27 | Status: complete | |
28 | Devices: [Data Translation] DT2814 (dt2814) | |
29 | ||
30 | Configuration options: | |
31 | [0] - I/O port base address | |
32 | [1] - IRQ | |
33 | ||
34 | This card has 16 analog inputs multiplexed onto a 12 bit ADC. There | |
35 | is a minimally useful onboard clock. The base frequency for the | |
36 | clock is selected by jumpers, and the clock divider can be selected | |
37 | via programmed I/O. Unfortunately, the clock divider can only be | |
38 | a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In | |
39 | addition, the clock does not seem to be very accurate. | |
40 | */ | |
41 | ||
25436dc9 | 42 | #include <linux/interrupt.h> |
a211ea97 DS |
43 | #include "../comedidev.h" |
44 | ||
45 | #include <linux/ioport.h> | |
46 | #include <linux/delay.h> | |
47 | ||
48 | #define DT2814_SIZE 2 | |
49 | ||
50 | #define DT2814_CSR 0 | |
51 | #define DT2814_DATA 1 | |
52 | ||
53 | /* | |
54 | * flags | |
55 | */ | |
56 | ||
57 | #define DT2814_FINISH 0x80 | |
58 | #define DT2814_ERR 0x40 | |
59 | #define DT2814_BUSY 0x20 | |
60 | #define DT2814_ENB 0x10 | |
61 | #define DT2814_CHANMASK 0x0f | |
62 | ||
0a85b6f0 MT |
63 | static int dt2814_attach(struct comedi_device *dev, |
64 | struct comedi_devconfig *it); | |
da91b269 | 65 | static int dt2814_detach(struct comedi_device *dev); |
139dfbdf | 66 | static struct comedi_driver driver_dt2814 = { |
68c3dbff BP |
67 | .driver_name = "dt2814", |
68 | .module = THIS_MODULE, | |
69 | .attach = dt2814_attach, | |
70 | .detach = dt2814_detach, | |
a211ea97 DS |
71 | }; |
72 | ||
7114a280 AT |
73 | static int __init driver_dt2814_init_module(void) |
74 | { | |
75 | return comedi_driver_register(&driver_dt2814); | |
76 | } | |
77 | ||
78 | static void __exit driver_dt2814_cleanup_module(void) | |
79 | { | |
80 | comedi_driver_unregister(&driver_dt2814); | |
81 | } | |
82 | ||
83 | module_init(driver_dt2814_init_module); | |
84 | module_exit(driver_dt2814_cleanup_module); | |
a211ea97 | 85 | |
70265d24 | 86 | static irqreturn_t dt2814_interrupt(int irq, void *dev); |
a211ea97 | 87 | |
ca6f10ca BP |
88 | struct dt2814_private { |
89 | ||
a211ea97 DS |
90 | int ntrig; |
91 | int curadchan; | |
ca6f10ca BP |
92 | }; |
93 | ||
94 | #define devpriv ((struct dt2814_private *)dev->private) | |
a211ea97 DS |
95 | |
96 | #define DT2814_TIMEOUT 10 | |
97 | #define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ | |
98 | ||
0a85b6f0 MT |
99 | static int dt2814_ai_insn_read(struct comedi_device *dev, |
100 | struct comedi_subdevice *s, | |
101 | struct comedi_insn *insn, unsigned int *data) | |
a211ea97 DS |
102 | { |
103 | int n, i, hi, lo; | |
104 | int chan; | |
105 | int status = 0; | |
106 | ||
107 | for (n = 0; n < insn->n; n++) { | |
108 | chan = CR_CHAN(insn->chanspec); | |
109 | ||
110 | outb(chan, dev->iobase + DT2814_CSR); | |
111 | for (i = 0; i < DT2814_TIMEOUT; i++) { | |
112 | status = inb(dev->iobase + DT2814_CSR); | |
3f3ba29c | 113 | printk(KERN_INFO "dt2814: status: %02x\n", status); |
5f74ea14 | 114 | udelay(10); |
a211ea97 DS |
115 | if (status & DT2814_FINISH) |
116 | break; | |
117 | } | |
118 | if (i >= DT2814_TIMEOUT) { | |
3f3ba29c | 119 | printk(KERN_INFO "dt2814: status: %02x\n", status); |
a211ea97 DS |
120 | return -ETIMEDOUT; |
121 | } | |
122 | ||
123 | hi = inb(dev->iobase + DT2814_DATA); | |
124 | lo = inb(dev->iobase + DT2814_DATA); | |
125 | ||
126 | data[n] = (hi << 4) | (lo >> 4); | |
127 | } | |
128 | ||
129 | return n; | |
130 | } | |
131 | ||
132 | static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) | |
133 | { | |
134 | int i; | |
135 | unsigned int f; | |
136 | ||
137 | /* XXX ignores flags */ | |
138 | ||
139 | f = 10000; /* ns */ | |
140 | for (i = 0; i < 8; i++) { | |
141 | if ((2 * (*ns)) < (f * 11)) | |
142 | break; | |
143 | f *= 10; | |
144 | } | |
145 | ||
146 | *ns = f; | |
147 | ||
148 | return i; | |
149 | } | |
150 | ||
0a85b6f0 MT |
151 | static int dt2814_ai_cmdtest(struct comedi_device *dev, |
152 | struct comedi_subdevice *s, struct comedi_cmd *cmd) | |
a211ea97 DS |
153 | { |
154 | int err = 0; | |
155 | int tmp; | |
156 | ||
157 | /* step 1: make sure trigger sources are trivially valid */ | |
158 | ||
159 | tmp = cmd->start_src; | |
160 | cmd->start_src &= TRIG_NOW; | |
161 | if (!cmd->start_src || tmp != cmd->start_src) | |
162 | err++; | |
163 | ||
164 | tmp = cmd->scan_begin_src; | |
165 | cmd->scan_begin_src &= TRIG_TIMER; | |
166 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
167 | err++; | |
168 | ||
169 | tmp = cmd->convert_src; | |
170 | cmd->convert_src &= TRIG_NOW; | |
171 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
172 | err++; | |
173 | ||
174 | tmp = cmd->scan_end_src; | |
175 | cmd->scan_end_src &= TRIG_COUNT; | |
176 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
177 | err++; | |
178 | ||
179 | tmp = cmd->stop_src; | |
180 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
181 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
182 | err++; | |
183 | ||
184 | if (err) | |
185 | return 1; | |
186 | ||
3f3ba29c FOT |
187 | /* step 2: make sure trigger sources are |
188 | * unique and mutually compatible */ | |
a211ea97 | 189 | |
828684f9 | 190 | /* note that mutual compatibility is not an issue here */ |
a211ea97 DS |
191 | if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) |
192 | err++; | |
193 | ||
194 | if (err) | |
195 | return 2; | |
196 | ||
197 | /* step 3: make sure arguments are trivially compatible */ | |
198 | ||
199 | if (cmd->start_arg != 0) { | |
200 | cmd->start_arg = 0; | |
201 | err++; | |
202 | } | |
203 | if (cmd->scan_begin_arg > 1000000000) { | |
204 | cmd->scan_begin_arg = 1000000000; | |
205 | err++; | |
206 | } | |
207 | if (cmd->scan_begin_arg < DT2814_MAX_SPEED) { | |
208 | cmd->scan_begin_arg = DT2814_MAX_SPEED; | |
209 | err++; | |
210 | } | |
211 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
212 | cmd->scan_end_arg = cmd->chanlist_len; | |
213 | err++; | |
214 | } | |
215 | if (cmd->stop_src == TRIG_COUNT) { | |
216 | if (cmd->stop_arg < 2) { | |
217 | cmd->stop_arg = 2; | |
218 | err++; | |
219 | } | |
220 | } else { | |
221 | /* TRIG_NONE */ | |
222 | if (cmd->stop_arg != 0) { | |
223 | cmd->stop_arg = 0; | |
224 | err++; | |
225 | } | |
226 | } | |
227 | ||
228 | if (err) | |
229 | return 3; | |
230 | ||
231 | /* step 4: fix up any arguments */ | |
232 | ||
233 | tmp = cmd->scan_begin_arg; | |
234 | dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); | |
235 | if (tmp != cmd->scan_begin_arg) | |
236 | err++; | |
237 | ||
238 | if (err) | |
239 | return 4; | |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
da91b269 | 244 | static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
a211ea97 | 245 | { |
ea6d0d4c | 246 | struct comedi_cmd *cmd = &s->async->cmd; |
a211ea97 DS |
247 | int chan; |
248 | int trigvar; | |
249 | ||
250 | trigvar = | |
0a85b6f0 MT |
251 | dt2814_ns_to_timer(&cmd->scan_begin_arg, |
252 | cmd->flags & TRIG_ROUND_MASK); | |
a211ea97 DS |
253 | |
254 | chan = CR_CHAN(cmd->chanlist[0]); | |
255 | ||
256 | devpriv->ntrig = cmd->stop_arg; | |
257 | outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); | |
258 | ||
259 | return 0; | |
260 | ||
261 | } | |
262 | ||
da91b269 | 263 | static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
a211ea97 DS |
264 | { |
265 | int i, irq; | |
266 | int ret; | |
34c43922 | 267 | struct comedi_subdevice *s; |
a211ea97 DS |
268 | unsigned long iobase; |
269 | ||
270 | iobase = it->options[0]; | |
3f3ba29c | 271 | printk(KERN_INFO "comedi%d: dt2814: 0x%04lx ", dev->minor, iobase); |
a211ea97 | 272 | if (!request_region(iobase, DT2814_SIZE, "dt2814")) { |
3f3ba29c | 273 | printk(KERN_ERR "I/O port conflict\n"); |
a211ea97 DS |
274 | return -EIO; |
275 | } | |
276 | dev->iobase = iobase; | |
277 | dev->board_name = "dt2814"; | |
278 | ||
279 | outb(0, dev->iobase + DT2814_CSR); | |
5f74ea14 | 280 | udelay(100); |
a211ea97 | 281 | if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) { |
3f3ba29c | 282 | printk(KERN_ERR "reset error (fatal)\n"); |
a211ea97 DS |
283 | return -EIO; |
284 | } | |
285 | i = inb(dev->iobase + DT2814_DATA); | |
286 | i = inb(dev->iobase + DT2814_DATA); | |
287 | ||
288 | irq = it->options[1]; | |
289 | #if 0 | |
290 | if (irq < 0) { | |
291 | save_flags(flags); | |
292 | sti(); | |
293 | irqs = probe_irq_on(); | |
294 | ||
295 | outb(0, dev->iobase + DT2814_CSR); | |
296 | ||
5f74ea14 | 297 | udelay(100); |
a211ea97 DS |
298 | |
299 | irq = probe_irq_off(irqs); | |
300 | restore_flags(flags); | |
3f3ba29c FOT |
301 | if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) |
302 | printk(KERN_DEBUG "error probing irq (bad)\n"); | |
303 | ||
a211ea97 DS |
304 | |
305 | i = inb(dev->iobase + DT2814_DATA); | |
306 | i = inb(dev->iobase + DT2814_DATA); | |
307 | } | |
308 | #endif | |
309 | dev->irq = 0; | |
310 | if (irq > 0) { | |
5f74ea14 | 311 | if (request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) { |
3f3ba29c | 312 | printk(KERN_WARNING "(irq %d unavailable)\n", irq); |
a211ea97 | 313 | } else { |
3f3ba29c | 314 | printk(KERN_INFO "( irq = %d )\n", irq); |
a211ea97 DS |
315 | dev->irq = irq; |
316 | } | |
317 | } else if (irq == 0) { | |
3f3ba29c | 318 | printk(KERN_WARNING "(no irq)\n"); |
a211ea97 DS |
319 | } else { |
320 | #if 0 | |
3f3ba29c | 321 | printk(KERN_DEBUG "(probe returned multiple irqs--bad)\n"); |
a211ea97 | 322 | #else |
3f3ba29c | 323 | printk(KERN_WARNING "(irq probe not implemented)\n"); |
a211ea97 DS |
324 | #endif |
325 | } | |
326 | ||
c3744138 BP |
327 | ret = alloc_subdevices(dev, 1); |
328 | if (ret < 0) | |
a211ea97 | 329 | return ret; |
c3744138 BP |
330 | |
331 | ret = alloc_private(dev, sizeof(struct dt2814_private)); | |
332 | if (ret < 0) | |
a211ea97 DS |
333 | return ret; |
334 | ||
335 | s = dev->subdevices + 0; | |
336 | dev->read_subdev = s; | |
337 | s->type = COMEDI_SUBD_AI; | |
338 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; | |
339 | s->n_chan = 16; /* XXX */ | |
340 | s->len_chanlist = 1; | |
341 | s->insn_read = dt2814_ai_insn_read; | |
342 | s->do_cmd = dt2814_ai_cmd; | |
343 | s->do_cmdtest = dt2814_ai_cmdtest; | |
344 | s->maxdata = 0xfff; | |
345 | s->range_table = &range_unknown; /* XXX */ | |
346 | ||
347 | return 0; | |
348 | } | |
349 | ||
da91b269 | 350 | static int dt2814_detach(struct comedi_device *dev) |
a211ea97 | 351 | { |
3f3ba29c | 352 | printk(KERN_INFO "comedi%d: dt2814: remove\n", dev->minor); |
a211ea97 | 353 | |
3f3ba29c | 354 | if (dev->irq) |
5f74ea14 | 355 | free_irq(dev->irq, dev); |
3f3ba29c FOT |
356 | |
357 | if (dev->iobase) | |
a211ea97 | 358 | release_region(dev->iobase, DT2814_SIZE); |
a211ea97 DS |
359 | |
360 | return 0; | |
361 | } | |
362 | ||
70265d24 | 363 | static irqreturn_t dt2814_interrupt(int irq, void *d) |
a211ea97 DS |
364 | { |
365 | int lo, hi; | |
71b5f4f1 | 366 | struct comedi_device *dev = d; |
34c43922 | 367 | struct comedi_subdevice *s; |
a211ea97 DS |
368 | int data; |
369 | ||
370 | if (!dev->attached) { | |
371 | comedi_error(dev, "spurious interrupt"); | |
372 | return IRQ_HANDLED; | |
373 | } | |
374 | ||
375 | s = dev->subdevices + 0; | |
376 | ||
377 | hi = inb(dev->iobase + DT2814_DATA); | |
378 | lo = inb(dev->iobase + DT2814_DATA); | |
379 | ||
380 | data = (hi << 4) | (lo >> 4); | |
381 | ||
382 | if (!(--devpriv->ntrig)) { | |
383 | int i; | |
384 | ||
385 | outb(0, dev->iobase + DT2814_CSR); | |
386 | /* note: turning off timed mode triggers another | |
387 | sample. */ | |
388 | ||
389 | for (i = 0; i < DT2814_TIMEOUT; i++) { | |
390 | if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH) | |
391 | break; | |
392 | } | |
393 | inb(dev->iobase + DT2814_DATA); | |
394 | inb(dev->iobase + DT2814_DATA); | |
395 | ||
396 | s->async->events |= COMEDI_CB_EOA; | |
397 | } | |
398 | comedi_event(dev, s); | |
399 | return IRQ_HANDLED; | |
400 | } | |
90f703d3 AT |
401 | |
402 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
403 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
404 | MODULE_LICENSE("GPL"); |