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