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 | ||
42 | #include "../comedidev.h" | |
43 | ||
44 | #include <linux/ioport.h> | |
45 | #include <linux/delay.h> | |
46 | ||
47 | #define DT2814_SIZE 2 | |
48 | ||
49 | #define DT2814_CSR 0 | |
50 | #define DT2814_DATA 1 | |
51 | ||
52 | /* | |
53 | * flags | |
54 | */ | |
55 | ||
56 | #define DT2814_FINISH 0x80 | |
57 | #define DT2814_ERR 0x40 | |
58 | #define DT2814_BUSY 0x20 | |
59 | #define DT2814_ENB 0x10 | |
60 | #define DT2814_CHANMASK 0x0f | |
61 | ||
0707bb04 | 62 | static int dt2814_attach(struct comedi_device * dev, struct comedi_devconfig * it); |
71b5f4f1 | 63 | static int dt2814_detach(struct comedi_device * dev); |
139dfbdf | 64 | static struct comedi_driver driver_dt2814 = { |
a211ea97 DS |
65 | driver_name:"dt2814", |
66 | module:THIS_MODULE, | |
67 | attach:dt2814_attach, | |
68 | detach:dt2814_detach, | |
69 | }; | |
70 | ||
71 | COMEDI_INITCLEANUP(driver_dt2814); | |
72 | ||
70265d24 | 73 | static irqreturn_t dt2814_interrupt(int irq, void *dev); |
a211ea97 | 74 | |
ca6f10ca BP |
75 | struct dt2814_private { |
76 | ||
a211ea97 DS |
77 | int ntrig; |
78 | int curadchan; | |
ca6f10ca BP |
79 | }; |
80 | ||
81 | #define devpriv ((struct dt2814_private *)dev->private) | |
a211ea97 DS |
82 | |
83 | #define DT2814_TIMEOUT 10 | |
84 | #define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ | |
85 | ||
34c43922 | 86 | static int dt2814_ai_insn_read(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 87 | struct comedi_insn * insn, unsigned int * data) |
a211ea97 DS |
88 | { |
89 | int n, i, hi, lo; | |
90 | int chan; | |
91 | int status = 0; | |
92 | ||
93 | for (n = 0; n < insn->n; n++) { | |
94 | chan = CR_CHAN(insn->chanspec); | |
95 | ||
96 | outb(chan, dev->iobase + DT2814_CSR); | |
97 | for (i = 0; i < DT2814_TIMEOUT; i++) { | |
98 | status = inb(dev->iobase + DT2814_CSR); | |
99 | printk("dt2814: status: %02x\n", status); | |
100 | comedi_udelay(10); | |
101 | if (status & DT2814_FINISH) | |
102 | break; | |
103 | } | |
104 | if (i >= DT2814_TIMEOUT) { | |
105 | printk("dt2814: status: %02x\n", status); | |
106 | return -ETIMEDOUT; | |
107 | } | |
108 | ||
109 | hi = inb(dev->iobase + DT2814_DATA); | |
110 | lo = inb(dev->iobase + DT2814_DATA); | |
111 | ||
112 | data[n] = (hi << 4) | (lo >> 4); | |
113 | } | |
114 | ||
115 | return n; | |
116 | } | |
117 | ||
118 | static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) | |
119 | { | |
120 | int i; | |
121 | unsigned int f; | |
122 | ||
123 | /* XXX ignores flags */ | |
124 | ||
125 | f = 10000; /* ns */ | |
126 | for (i = 0; i < 8; i++) { | |
127 | if ((2 * (*ns)) < (f * 11)) | |
128 | break; | |
129 | f *= 10; | |
130 | } | |
131 | ||
132 | *ns = f; | |
133 | ||
134 | return i; | |
135 | } | |
136 | ||
34c43922 | 137 | static int dt2814_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 138 | struct comedi_cmd * cmd) |
a211ea97 DS |
139 | { |
140 | int err = 0; | |
141 | int tmp; | |
142 | ||
143 | /* step 1: make sure trigger sources are trivially valid */ | |
144 | ||
145 | tmp = cmd->start_src; | |
146 | cmd->start_src &= TRIG_NOW; | |
147 | if (!cmd->start_src || tmp != cmd->start_src) | |
148 | err++; | |
149 | ||
150 | tmp = cmd->scan_begin_src; | |
151 | cmd->scan_begin_src &= TRIG_TIMER; | |
152 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
153 | err++; | |
154 | ||
155 | tmp = cmd->convert_src; | |
156 | cmd->convert_src &= TRIG_NOW; | |
157 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
158 | err++; | |
159 | ||
160 | tmp = cmd->scan_end_src; | |
161 | cmd->scan_end_src &= TRIG_COUNT; | |
162 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
163 | err++; | |
164 | ||
165 | tmp = cmd->stop_src; | |
166 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
167 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
168 | err++; | |
169 | ||
170 | if (err) | |
171 | return 1; | |
172 | ||
173 | /* step 2: make sure trigger sources are unique and mutually compatible */ | |
174 | ||
175 | /* note that mutual compatiblity is not an issue here */ | |
176 | if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) | |
177 | err++; | |
178 | ||
179 | if (err) | |
180 | return 2; | |
181 | ||
182 | /* step 3: make sure arguments are trivially compatible */ | |
183 | ||
184 | if (cmd->start_arg != 0) { | |
185 | cmd->start_arg = 0; | |
186 | err++; | |
187 | } | |
188 | if (cmd->scan_begin_arg > 1000000000) { | |
189 | cmd->scan_begin_arg = 1000000000; | |
190 | err++; | |
191 | } | |
192 | if (cmd->scan_begin_arg < DT2814_MAX_SPEED) { | |
193 | cmd->scan_begin_arg = DT2814_MAX_SPEED; | |
194 | err++; | |
195 | } | |
196 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
197 | cmd->scan_end_arg = cmd->chanlist_len; | |
198 | err++; | |
199 | } | |
200 | if (cmd->stop_src == TRIG_COUNT) { | |
201 | if (cmd->stop_arg < 2) { | |
202 | cmd->stop_arg = 2; | |
203 | err++; | |
204 | } | |
205 | } else { | |
206 | /* TRIG_NONE */ | |
207 | if (cmd->stop_arg != 0) { | |
208 | cmd->stop_arg = 0; | |
209 | err++; | |
210 | } | |
211 | } | |
212 | ||
213 | if (err) | |
214 | return 3; | |
215 | ||
216 | /* step 4: fix up any arguments */ | |
217 | ||
218 | tmp = cmd->scan_begin_arg; | |
219 | dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); | |
220 | if (tmp != cmd->scan_begin_arg) | |
221 | err++; | |
222 | ||
223 | if (err) | |
224 | return 4; | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
34c43922 | 229 | static int dt2814_ai_cmd(struct comedi_device * dev, struct comedi_subdevice * s) |
a211ea97 | 230 | { |
ea6d0d4c | 231 | struct comedi_cmd *cmd = &s->async->cmd; |
a211ea97 DS |
232 | int chan; |
233 | int trigvar; | |
234 | ||
235 | trigvar = | |
236 | dt2814_ns_to_timer(&cmd->scan_begin_arg, | |
237 | cmd->flags & TRIG_ROUND_MASK); | |
238 | ||
239 | chan = CR_CHAN(cmd->chanlist[0]); | |
240 | ||
241 | devpriv->ntrig = cmd->stop_arg; | |
242 | outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); | |
243 | ||
244 | return 0; | |
245 | ||
246 | } | |
247 | ||
0707bb04 | 248 | static int dt2814_attach(struct comedi_device * dev, struct comedi_devconfig * it) |
a211ea97 DS |
249 | { |
250 | int i, irq; | |
251 | int ret; | |
34c43922 | 252 | struct comedi_subdevice *s; |
a211ea97 DS |
253 | unsigned long iobase; |
254 | ||
255 | iobase = it->options[0]; | |
256 | printk("comedi%d: dt2814: 0x%04lx ", dev->minor, iobase); | |
257 | if (!request_region(iobase, DT2814_SIZE, "dt2814")) { | |
258 | printk("I/O port conflict\n"); | |
259 | return -EIO; | |
260 | } | |
261 | dev->iobase = iobase; | |
262 | dev->board_name = "dt2814"; | |
263 | ||
264 | outb(0, dev->iobase + DT2814_CSR); | |
265 | comedi_udelay(100); | |
266 | if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) { | |
267 | printk("reset error (fatal)\n"); | |
268 | return -EIO; | |
269 | } | |
270 | i = inb(dev->iobase + DT2814_DATA); | |
271 | i = inb(dev->iobase + DT2814_DATA); | |
272 | ||
273 | irq = it->options[1]; | |
274 | #if 0 | |
275 | if (irq < 0) { | |
276 | save_flags(flags); | |
277 | sti(); | |
278 | irqs = probe_irq_on(); | |
279 | ||
280 | outb(0, dev->iobase + DT2814_CSR); | |
281 | ||
282 | comedi_udelay(100); | |
283 | ||
284 | irq = probe_irq_off(irqs); | |
285 | restore_flags(flags); | |
286 | if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) { | |
287 | printk("error probing irq (bad) \n"); | |
288 | } | |
289 | ||
290 | i = inb(dev->iobase + DT2814_DATA); | |
291 | i = inb(dev->iobase + DT2814_DATA); | |
292 | } | |
293 | #endif | |
294 | dev->irq = 0; | |
295 | if (irq > 0) { | |
296 | if (comedi_request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) { | |
297 | printk("(irq %d unavailable)\n", irq); | |
298 | } else { | |
299 | printk("( irq = %d )\n", irq); | |
300 | dev->irq = irq; | |
301 | } | |
302 | } else if (irq == 0) { | |
303 | printk("(no irq)\n"); | |
304 | } else { | |
305 | #if 0 | |
306 | printk("(probe returned multiple irqs--bad)\n"); | |
307 | #else | |
308 | printk("(irq probe not implemented)\n"); | |
309 | #endif | |
310 | } | |
311 | ||
312 | if ((ret = alloc_subdevices(dev, 1)) < 0) | |
313 | return ret; | |
ca6f10ca | 314 | if ((ret = alloc_private(dev, sizeof(struct dt2814_private))) < 0) |
a211ea97 DS |
315 | return ret; |
316 | ||
317 | s = dev->subdevices + 0; | |
318 | dev->read_subdev = s; | |
319 | s->type = COMEDI_SUBD_AI; | |
320 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; | |
321 | s->n_chan = 16; /* XXX */ | |
322 | s->len_chanlist = 1; | |
323 | s->insn_read = dt2814_ai_insn_read; | |
324 | s->do_cmd = dt2814_ai_cmd; | |
325 | s->do_cmdtest = dt2814_ai_cmdtest; | |
326 | s->maxdata = 0xfff; | |
327 | s->range_table = &range_unknown; /* XXX */ | |
328 | ||
329 | return 0; | |
330 | } | |
331 | ||
71b5f4f1 | 332 | static int dt2814_detach(struct comedi_device * dev) |
a211ea97 DS |
333 | { |
334 | printk("comedi%d: dt2814: remove\n", dev->minor); | |
335 | ||
336 | if (dev->irq) { | |
337 | comedi_free_irq(dev->irq, dev); | |
338 | } | |
339 | if (dev->iobase) { | |
340 | release_region(dev->iobase, DT2814_SIZE); | |
341 | } | |
342 | ||
343 | return 0; | |
344 | } | |
345 | ||
70265d24 | 346 | static irqreturn_t dt2814_interrupt(int irq, void *d) |
a211ea97 DS |
347 | { |
348 | int lo, hi; | |
71b5f4f1 | 349 | struct comedi_device *dev = d; |
34c43922 | 350 | struct comedi_subdevice *s; |
a211ea97 DS |
351 | int data; |
352 | ||
353 | if (!dev->attached) { | |
354 | comedi_error(dev, "spurious interrupt"); | |
355 | return IRQ_HANDLED; | |
356 | } | |
357 | ||
358 | s = dev->subdevices + 0; | |
359 | ||
360 | hi = inb(dev->iobase + DT2814_DATA); | |
361 | lo = inb(dev->iobase + DT2814_DATA); | |
362 | ||
363 | data = (hi << 4) | (lo >> 4); | |
364 | ||
365 | if (!(--devpriv->ntrig)) { | |
366 | int i; | |
367 | ||
368 | outb(0, dev->iobase + DT2814_CSR); | |
369 | /* note: turning off timed mode triggers another | |
370 | sample. */ | |
371 | ||
372 | for (i = 0; i < DT2814_TIMEOUT; i++) { | |
373 | if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH) | |
374 | break; | |
375 | } | |
376 | inb(dev->iobase + DT2814_DATA); | |
377 | inb(dev->iobase + DT2814_DATA); | |
378 | ||
379 | s->async->events |= COMEDI_CB_EOA; | |
380 | } | |
381 | comedi_event(dev, s); | |
382 | return IRQ_HANDLED; | |
383 | } |