Commit | Line | Data |
---|---|---|
7ff7e4c2 IA |
1 | /* |
2 | comedi/drivers/amplc_dio200_common.c | |
3 | ||
4 | Common support code for "amplc_dio200" and "amplc_dio200_pci". | |
5 | ||
6 | Copyright (C) 2005-2013 MEV Ltd. <http://www.mev.co.uk/> | |
7 | ||
8 | COMEDI - Linux Control and Measurement Device Interface | |
9 | Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org> | |
10 | ||
11 | This program is free software; you can redistribute it and/or modify | |
12 | it under the terms of the GNU General Public License as published by | |
13 | the Free Software Foundation; either version 2 of the License, or | |
14 | (at your option) any later version. | |
15 | ||
16 | This program is distributed in the hope that it will be useful, | |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | GNU General Public License for more details. | |
7ff7e4c2 IA |
20 | */ |
21 | ||
ce157f80 | 22 | #include <linux/module.h> |
7ff7e4c2 | 23 | #include <linux/interrupt.h> |
7ff7e4c2 IA |
24 | |
25 | #include "../comedidev.h" | |
26 | ||
27 | #include "amplc_dio200.h" | |
28 | #include "comedi_fc.h" | |
29 | #include "8253.h" | |
f0162091 | 30 | #include "8255.h" /* only for register defines */ |
7ff7e4c2 IA |
31 | |
32 | /* 200 series registers */ | |
33 | #define DIO200_IO_SIZE 0x20 | |
34 | #define DIO200_PCIE_IO_SIZE 0x4000 | |
35 | #define DIO200_XCLK_SCE 0x18 /* Group X clock selection register */ | |
36 | #define DIO200_YCLK_SCE 0x19 /* Group Y clock selection register */ | |
37 | #define DIO200_ZCLK_SCE 0x1a /* Group Z clock selection register */ | |
38 | #define DIO200_XGAT_SCE 0x1b /* Group X gate selection register */ | |
39 | #define DIO200_YGAT_SCE 0x1c /* Group Y gate selection register */ | |
40 | #define DIO200_ZGAT_SCE 0x1d /* Group Z gate selection register */ | |
41 | #define DIO200_INT_SCE 0x1e /* Interrupt enable/status register */ | |
42 | /* Extra registers for new PCIe boards */ | |
43 | #define DIO200_ENHANCE 0x20 /* 1 to enable enhanced features */ | |
44 | #define DIO200_VERSION 0x24 /* Hardware version register */ | |
45 | #define DIO200_TS_CONFIG 0x600 /* Timestamp timer config register */ | |
46 | #define DIO200_TS_COUNT 0x602 /* Timestamp timer count register */ | |
47 | ||
48 | /* | |
49 | * Functions for constructing value for DIO_200_?CLK_SCE and | |
50 | * DIO_200_?GAT_SCE registers: | |
51 | * | |
52 | * 'which' is: 0 for CTR-X1, CTR-Y1, CTR-Z1; 1 for CTR-X2, CTR-Y2 or CTR-Z2. | |
53 | * 'chan' is the channel: 0, 1 or 2. | |
54 | * 'source' is the signal source: 0 to 7, or 0 to 31 for "enhanced" boards. | |
55 | */ | |
56 | static unsigned char clk_gat_sce(unsigned int which, unsigned int chan, | |
57 | unsigned int source) | |
58 | { | |
59 | return (which << 5) | (chan << 3) | | |
60 | ((source & 030) << 3) | (source & 007); | |
61 | } | |
62 | ||
63 | static unsigned char clk_sce(unsigned int which, unsigned int chan, | |
64 | unsigned int source) | |
65 | { | |
66 | return clk_gat_sce(which, chan, source); | |
67 | } | |
68 | ||
69 | static unsigned char gat_sce(unsigned int which, unsigned int chan, | |
70 | unsigned int source) | |
71 | { | |
72 | return clk_gat_sce(which, chan, source); | |
73 | } | |
74 | ||
75 | /* | |
76 | * Periods of the internal clock sources in nanoseconds. | |
77 | */ | |
78 | static const unsigned int clock_period[32] = { | |
79 | [1] = 100, /* 10 MHz */ | |
80 | [2] = 1000, /* 1 MHz */ | |
81 | [3] = 10000, /* 100 kHz */ | |
82 | [4] = 100000, /* 10 kHz */ | |
83 | [5] = 1000000, /* 1 kHz */ | |
84 | [11] = 50, /* 20 MHz (enhanced boards) */ | |
85 | /* clock sources 12 and later reserved for enhanced boards */ | |
86 | }; | |
87 | ||
88 | /* | |
89 | * Timestamp timer configuration register (for new PCIe boards). | |
90 | */ | |
91 | #define TS_CONFIG_RESET 0x100 /* Reset counter to zero. */ | |
92 | #define TS_CONFIG_CLK_SRC_MASK 0x0FF /* Clock source. */ | |
93 | #define TS_CONFIG_MAX_CLK_SRC 2 /* Maximum clock source value. */ | |
94 | ||
95 | /* | |
96 | * Periods of the timestamp timer clock sources in nanoseconds. | |
97 | */ | |
98 | static const unsigned int ts_clock_period[TS_CONFIG_MAX_CLK_SRC + 1] = { | |
99 | 1, /* 1 nanosecond (but with 20 ns granularity). */ | |
100 | 1000, /* 1 microsecond. */ | |
101 | 1000000, /* 1 millisecond. */ | |
102 | }; | |
103 | ||
104 | struct dio200_subdev_8254 { | |
105 | unsigned int ofs; /* Counter base offset */ | |
106 | unsigned int clk_sce_ofs; /* CLK_SCE base address */ | |
107 | unsigned int gat_sce_ofs; /* GAT_SCE base address */ | |
108 | int which; /* Bit 5 of CLK_SCE or GAT_SCE */ | |
109 | unsigned int clock_src[3]; /* Current clock sources */ | |
110 | unsigned int gate_src[3]; /* Current gate sources */ | |
111 | spinlock_t spinlock; | |
112 | }; | |
113 | ||
114 | struct dio200_subdev_8255 { | |
115 | unsigned int ofs; /* DIO base offset */ | |
116 | }; | |
117 | ||
118 | struct dio200_subdev_intr { | |
119 | spinlock_t spinlock; | |
120 | unsigned int ofs; | |
121 | unsigned int valid_isns; | |
122 | unsigned int enabled_isns; | |
123 | unsigned int stopcount; | |
124 | bool active:1; | |
7ff7e4c2 IA |
125 | }; |
126 | ||
7ff7e4c2 IA |
127 | static unsigned char dio200_read8(struct comedi_device *dev, |
128 | unsigned int offset) | |
129 | { | |
058543b7 | 130 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 | 131 | |
c3f6aa33 HS |
132 | if (board->is_pcie) |
133 | offset <<= 3; | |
18a8e8c5 | 134 | |
0c3dfdc2 HS |
135 | if (dev->mmio) |
136 | return readb(dev->mmio + offset); | |
137 | return inb(dev->iobase + offset); | |
7ff7e4c2 IA |
138 | } |
139 | ||
42c6767b HS |
140 | static void dio200_write8(struct comedi_device *dev, |
141 | unsigned int offset, unsigned char val) | |
7ff7e4c2 | 142 | { |
058543b7 | 143 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 | 144 | |
c3f6aa33 HS |
145 | if (board->is_pcie) |
146 | offset <<= 3; | |
0c3dfdc2 HS |
147 | |
148 | if (dev->mmio) | |
149 | writeb(val, dev->mmio + offset); | |
7ff7e4c2 | 150 | else |
0c3dfdc2 | 151 | outb(val, dev->iobase + offset); |
7ff7e4c2 IA |
152 | } |
153 | ||
7ff7e4c2 IA |
154 | static unsigned int dio200_read32(struct comedi_device *dev, |
155 | unsigned int offset) | |
156 | { | |
058543b7 | 157 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 | 158 | |
c3f6aa33 HS |
159 | if (board->is_pcie) |
160 | offset <<= 3; | |
18a8e8c5 | 161 | |
0c3dfdc2 HS |
162 | if (dev->mmio) |
163 | return readl(dev->mmio + offset); | |
164 | return inl(dev->iobase + offset); | |
7ff7e4c2 IA |
165 | } |
166 | ||
42c6767b HS |
167 | static void dio200_write32(struct comedi_device *dev, |
168 | unsigned int offset, unsigned int val) | |
7ff7e4c2 | 169 | { |
058543b7 | 170 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 | 171 | |
c3f6aa33 HS |
172 | if (board->is_pcie) |
173 | offset <<= 3; | |
0c3dfdc2 HS |
174 | |
175 | if (dev->mmio) | |
176 | writel(val, dev->mmio + offset); | |
7ff7e4c2 | 177 | else |
0c3dfdc2 | 178 | outl(val, dev->iobase + offset); |
7ff7e4c2 IA |
179 | } |
180 | ||
42c6767b HS |
181 | static int dio200_subdev_intr_insn_bits(struct comedi_device *dev, |
182 | struct comedi_subdevice *s, | |
183 | struct comedi_insn *insn, | |
184 | unsigned int *data) | |
7ff7e4c2 | 185 | { |
058543b7 | 186 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
187 | struct dio200_subdev_intr *subpriv = s->private; |
188 | ||
f6ce0950 | 189 | if (board->has_int_sce) { |
7ff7e4c2 IA |
190 | /* Just read the interrupt status register. */ |
191 | data[1] = dio200_read8(dev, subpriv->ofs) & subpriv->valid_isns; | |
192 | } else { | |
193 | /* No interrupt status register. */ | |
194 | data[0] = 0; | |
195 | } | |
196 | ||
197 | return insn->n; | |
198 | } | |
199 | ||
7ff7e4c2 IA |
200 | static void dio200_stop_intr(struct comedi_device *dev, |
201 | struct comedi_subdevice *s) | |
202 | { | |
058543b7 | 203 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
204 | struct dio200_subdev_intr *subpriv = s->private; |
205 | ||
206 | subpriv->active = false; | |
207 | subpriv->enabled_isns = 0; | |
f6ce0950 | 208 | if (board->has_int_sce) |
7ff7e4c2 IA |
209 | dio200_write8(dev, subpriv->ofs, 0); |
210 | } | |
211 | ||
157a340d HS |
212 | static void dio200_start_intr(struct comedi_device *dev, |
213 | struct comedi_subdevice *s) | |
7ff7e4c2 | 214 | { |
058543b7 | 215 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
216 | struct dio200_subdev_intr *subpriv = s->private; |
217 | struct comedi_cmd *cmd = &s->async->cmd; | |
f6ce0950 HS |
218 | unsigned int n; |
219 | unsigned isn_bits; | |
7ff7e4c2 | 220 | |
75d756e9 HS |
221 | /* Determine interrupt sources to enable. */ |
222 | isn_bits = 0; | |
223 | if (cmd->chanlist) { | |
224 | for (n = 0; n < cmd->chanlist_len; n++) | |
225 | isn_bits |= (1U << CR_CHAN(cmd->chanlist[n])); | |
7ff7e4c2 | 226 | } |
75d756e9 HS |
227 | isn_bits &= subpriv->valid_isns; |
228 | /* Enable interrupt sources. */ | |
229 | subpriv->enabled_isns = isn_bits; | |
230 | if (board->has_int_sce) | |
231 | dio200_write8(dev, subpriv->ofs, isn_bits); | |
7ff7e4c2 IA |
232 | } |
233 | ||
ebe0f68e HS |
234 | static int dio200_inttrig_start_intr(struct comedi_device *dev, |
235 | struct comedi_subdevice *s, | |
236 | unsigned int trig_num) | |
7ff7e4c2 | 237 | { |
ebe0f68e HS |
238 | struct dio200_subdev_intr *subpriv = s->private; |
239 | struct comedi_cmd *cmd = &s->async->cmd; | |
7ff7e4c2 | 240 | unsigned long flags; |
7ff7e4c2 | 241 | |
ebe0f68e | 242 | if (trig_num != cmd->start_arg) |
7ff7e4c2 IA |
243 | return -EINVAL; |
244 | ||
7ff7e4c2 IA |
245 | spin_lock_irqsave(&subpriv->spinlock, flags); |
246 | s->async->inttrig = NULL; | |
247 | if (subpriv->active) | |
157a340d | 248 | dio200_start_intr(dev, s); |
7ff7e4c2 IA |
249 | |
250 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
251 | ||
7ff7e4c2 IA |
252 | return 1; |
253 | } | |
254 | ||
255 | static void dio200_read_scan_intr(struct comedi_device *dev, | |
256 | struct comedi_subdevice *s, | |
257 | unsigned int triggered) | |
258 | { | |
259 | struct dio200_subdev_intr *subpriv = s->private; | |
22a27048 | 260 | struct comedi_cmd *cmd = &s->async->cmd; |
7ff7e4c2 | 261 | unsigned short val; |
71ba7506 | 262 | unsigned int n, ch; |
7ff7e4c2 IA |
263 | |
264 | val = 0; | |
71ba7506 HS |
265 | for (n = 0; n < cmd->chanlist_len; n++) { |
266 | ch = CR_CHAN(cmd->chanlist[n]); | |
7ff7e4c2 IA |
267 | if (triggered & (1U << ch)) |
268 | val |= (1U << n); | |
269 | } | |
270 | /* Write the scan to the buffer. */ | |
3672effd | 271 | if (comedi_buf_put(s, val)) { |
7ff7e4c2 IA |
272 | s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS); |
273 | } else { | |
274 | /* Error! Stop acquisition. */ | |
275 | dio200_stop_intr(dev, s); | |
276 | s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; | |
12c2791b | 277 | dev_err(dev->class_dev, "buffer overflow\n"); |
7ff7e4c2 IA |
278 | } |
279 | ||
280 | /* Check for end of acquisition. */ | |
22a27048 | 281 | if (cmd->stop_src == TRIG_COUNT) { |
7ff7e4c2 IA |
282 | if (subpriv->stopcount > 0) { |
283 | subpriv->stopcount--; | |
284 | if (subpriv->stopcount == 0) { | |
285 | s->async->events |= COMEDI_CB_EOA; | |
286 | dio200_stop_intr(dev, s); | |
287 | } | |
288 | } | |
289 | } | |
290 | } | |
291 | ||
7ff7e4c2 IA |
292 | static int dio200_handle_read_intr(struct comedi_device *dev, |
293 | struct comedi_subdevice *s) | |
294 | { | |
058543b7 | 295 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
296 | struct dio200_subdev_intr *subpriv = s->private; |
297 | unsigned triggered; | |
298 | unsigned intstat; | |
299 | unsigned cur_enabled; | |
300 | unsigned int oldevents; | |
301 | unsigned long flags; | |
302 | ||
303 | triggered = 0; | |
304 | ||
305 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
306 | oldevents = s->async->events; | |
f6ce0950 | 307 | if (board->has_int_sce) { |
7ff7e4c2 IA |
308 | /* |
309 | * Collect interrupt sources that have triggered and disable | |
310 | * them temporarily. Loop around until no extra interrupt | |
311 | * sources have triggered, at which point, the valid part of | |
312 | * the interrupt status register will read zero, clearing the | |
313 | * cause of the interrupt. | |
314 | * | |
315 | * Mask off interrupt sources already seen to avoid infinite | |
316 | * loop in case of misconfiguration. | |
317 | */ | |
318 | cur_enabled = subpriv->enabled_isns; | |
319 | while ((intstat = (dio200_read8(dev, subpriv->ofs) & | |
320 | subpriv->valid_isns & ~triggered)) != 0) { | |
321 | triggered |= intstat; | |
322 | cur_enabled &= ~triggered; | |
323 | dio200_write8(dev, subpriv->ofs, cur_enabled); | |
324 | } | |
325 | } else { | |
326 | /* | |
327 | * No interrupt status register. Assume the single interrupt | |
328 | * source has triggered. | |
329 | */ | |
330 | triggered = subpriv->enabled_isns; | |
331 | } | |
332 | ||
333 | if (triggered) { | |
334 | /* | |
335 | * Some interrupt sources have triggered and have been | |
336 | * temporarily disabled to clear the cause of the interrupt. | |
337 | * | |
338 | * Reenable them NOW to minimize the time they are disabled. | |
339 | */ | |
340 | cur_enabled = subpriv->enabled_isns; | |
f6ce0950 | 341 | if (board->has_int_sce) |
7ff7e4c2 IA |
342 | dio200_write8(dev, subpriv->ofs, cur_enabled); |
343 | ||
344 | if (subpriv->active) { | |
345 | /* | |
346 | * The command is still active. | |
347 | * | |
348 | * Ignore interrupt sources that the command isn't | |
349 | * interested in (just in case there's a race | |
350 | * condition). | |
351 | */ | |
352 | if (triggered & subpriv->enabled_isns) | |
353 | /* Collect scan data. */ | |
354 | dio200_read_scan_intr(dev, s, triggered); | |
355 | } | |
356 | } | |
357 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
358 | ||
359 | if (oldevents != s->async->events) | |
360 | comedi_event(dev, s); | |
361 | ||
362 | return (triggered != 0); | |
363 | } | |
364 | ||
7ff7e4c2 IA |
365 | static int dio200_subdev_intr_cancel(struct comedi_device *dev, |
366 | struct comedi_subdevice *s) | |
367 | { | |
368 | struct dio200_subdev_intr *subpriv = s->private; | |
369 | unsigned long flags; | |
370 | ||
371 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
372 | if (subpriv->active) | |
373 | dio200_stop_intr(dev, s); | |
374 | ||
375 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
376 | ||
377 | return 0; | |
378 | } | |
379 | ||
42c6767b HS |
380 | static int dio200_subdev_intr_cmdtest(struct comedi_device *dev, |
381 | struct comedi_subdevice *s, | |
382 | struct comedi_cmd *cmd) | |
7ff7e4c2 IA |
383 | { |
384 | int err = 0; | |
385 | ||
386 | /* Step 1 : check if triggers are trivially valid */ | |
387 | ||
388 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT); | |
389 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); | |
390 | err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); | |
391 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
392 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); | |
393 | ||
394 | if (err) | |
395 | return 1; | |
396 | ||
397 | /* Step 2a : make sure trigger sources are unique */ | |
398 | ||
399 | err |= cfc_check_trigger_is_unique(cmd->start_src); | |
400 | err |= cfc_check_trigger_is_unique(cmd->stop_src); | |
401 | ||
402 | /* Step 2b : and mutually compatible */ | |
403 | ||
404 | if (err) | |
405 | return 2; | |
406 | ||
407 | /* Step 3: check if arguments are trivially valid */ | |
408 | ||
409 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); | |
410 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
411 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); | |
412 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); | |
413 | ||
75d756e9 HS |
414 | if (cmd->stop_src == TRIG_COUNT) |
415 | err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); | |
416 | else /* TRIG_NONE */ | |
7ff7e4c2 | 417 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); |
7ff7e4c2 IA |
418 | |
419 | if (err) | |
420 | return 3; | |
421 | ||
422 | /* step 4: fix up any arguments */ | |
423 | ||
424 | /* if (err) return 4; */ | |
425 | ||
426 | return 0; | |
427 | } | |
428 | ||
7ff7e4c2 IA |
429 | static int dio200_subdev_intr_cmd(struct comedi_device *dev, |
430 | struct comedi_subdevice *s) | |
431 | { | |
432 | struct comedi_cmd *cmd = &s->async->cmd; | |
433 | struct dio200_subdev_intr *subpriv = s->private; | |
434 | unsigned long flags; | |
7ff7e4c2 IA |
435 | |
436 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
7ff7e4c2 | 437 | |
06f55bb7 HS |
438 | subpriv->active = true; |
439 | subpriv->stopcount = cmd->stop_arg; | |
7ff7e4c2 | 440 | |
ebe0f68e | 441 | if (cmd->start_src == TRIG_INT) |
7ff7e4c2 | 442 | s->async->inttrig = dio200_inttrig_start_intr; |
ebe0f68e | 443 | else /* TRIG_NOW */ |
157a340d | 444 | dio200_start_intr(dev, s); |
ebe0f68e | 445 | |
7ff7e4c2 IA |
446 | spin_unlock_irqrestore(&subpriv->spinlock, flags); |
447 | ||
7ff7e4c2 IA |
448 | return 0; |
449 | } | |
450 | ||
42c6767b HS |
451 | static int dio200_subdev_intr_init(struct comedi_device *dev, |
452 | struct comedi_subdevice *s, | |
453 | unsigned int offset, | |
454 | unsigned valid_isns) | |
7ff7e4c2 | 455 | { |
058543b7 | 456 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
457 | struct dio200_subdev_intr *subpriv; |
458 | ||
0480bcb9 | 459 | subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); |
7ff7e4c2 IA |
460 | if (!subpriv) |
461 | return -ENOMEM; | |
462 | ||
463 | subpriv->ofs = offset; | |
464 | subpriv->valid_isns = valid_isns; | |
465 | spin_lock_init(&subpriv->spinlock); | |
466 | ||
f6ce0950 | 467 | if (board->has_int_sce) |
7ff7e4c2 IA |
468 | /* Disable interrupt sources. */ |
469 | dio200_write8(dev, subpriv->ofs, 0); | |
470 | ||
7ff7e4c2 IA |
471 | s->type = COMEDI_SUBD_DI; |
472 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; | |
f6ce0950 | 473 | if (board->has_int_sce) { |
7ff7e4c2 IA |
474 | s->n_chan = DIO200_MAX_ISNS; |
475 | s->len_chanlist = DIO200_MAX_ISNS; | |
476 | } else { | |
477 | /* No interrupt source register. Support single channel. */ | |
478 | s->n_chan = 1; | |
479 | s->len_chanlist = 1; | |
480 | } | |
481 | s->range_table = &range_digital; | |
482 | s->maxdata = 1; | |
483 | s->insn_bits = dio200_subdev_intr_insn_bits; | |
484 | s->do_cmdtest = dio200_subdev_intr_cmdtest; | |
485 | s->do_cmd = dio200_subdev_intr_cmd; | |
486 | s->cancel = dio200_subdev_intr_cancel; | |
487 | ||
488 | return 0; | |
489 | } | |
490 | ||
7ff7e4c2 IA |
491 | static irqreturn_t dio200_interrupt(int irq, void *d) |
492 | { | |
493 | struct comedi_device *dev = d; | |
76212bf3 | 494 | struct comedi_subdevice *s = dev->read_subdev; |
7ff7e4c2 IA |
495 | int handled; |
496 | ||
497 | if (!dev->attached) | |
498 | return IRQ_NONE; | |
499 | ||
76212bf3 | 500 | handled = dio200_handle_read_intr(dev, s); |
7ff7e4c2 IA |
501 | |
502 | return IRQ_RETVAL(handled); | |
503 | } | |
504 | ||
42c6767b HS |
505 | static unsigned int dio200_subdev_8254_read_chan(struct comedi_device *dev, |
506 | struct comedi_subdevice *s, | |
507 | unsigned int chan) | |
7ff7e4c2 IA |
508 | { |
509 | struct dio200_subdev_8254 *subpriv = s->private; | |
510 | unsigned int val; | |
511 | ||
512 | /* latch counter */ | |
513 | val = chan << 6; | |
514 | dio200_write8(dev, subpriv->ofs + i8254_control_reg, val); | |
515 | /* read lsb, msb */ | |
516 | val = dio200_read8(dev, subpriv->ofs + chan); | |
517 | val += dio200_read8(dev, subpriv->ofs + chan) << 8; | |
518 | return val; | |
519 | } | |
520 | ||
42c6767b HS |
521 | static void dio200_subdev_8254_write_chan(struct comedi_device *dev, |
522 | struct comedi_subdevice *s, | |
523 | unsigned int chan, | |
524 | unsigned int count) | |
7ff7e4c2 IA |
525 | { |
526 | struct dio200_subdev_8254 *subpriv = s->private; | |
527 | ||
528 | /* write lsb, msb */ | |
529 | dio200_write8(dev, subpriv->ofs + chan, count & 0xff); | |
530 | dio200_write8(dev, subpriv->ofs + chan, (count >> 8) & 0xff); | |
531 | } | |
532 | ||
42c6767b HS |
533 | static void dio200_subdev_8254_set_mode(struct comedi_device *dev, |
534 | struct comedi_subdevice *s, | |
535 | unsigned int chan, | |
536 | unsigned int mode) | |
7ff7e4c2 IA |
537 | { |
538 | struct dio200_subdev_8254 *subpriv = s->private; | |
539 | unsigned int byte; | |
540 | ||
541 | byte = chan << 6; | |
542 | byte |= 0x30; /* access order: lsb, msb */ | |
543 | byte |= (mode & 0xf); /* counter mode and BCD|binary */ | |
544 | dio200_write8(dev, subpriv->ofs + i8254_control_reg, byte); | |
545 | } | |
546 | ||
42c6767b HS |
547 | static unsigned int dio200_subdev_8254_status(struct comedi_device *dev, |
548 | struct comedi_subdevice *s, | |
549 | unsigned int chan) | |
7ff7e4c2 IA |
550 | { |
551 | struct dio200_subdev_8254 *subpriv = s->private; | |
552 | ||
553 | /* latch status */ | |
554 | dio200_write8(dev, subpriv->ofs + i8254_control_reg, | |
555 | 0xe0 | (2 << chan)); | |
556 | /* read status */ | |
557 | return dio200_read8(dev, subpriv->ofs + chan); | |
558 | } | |
559 | ||
42c6767b HS |
560 | static int dio200_subdev_8254_read(struct comedi_device *dev, |
561 | struct comedi_subdevice *s, | |
562 | struct comedi_insn *insn, | |
563 | unsigned int *data) | |
7ff7e4c2 IA |
564 | { |
565 | struct dio200_subdev_8254 *subpriv = s->private; | |
566 | int chan = CR_CHAN(insn->chanspec); | |
567 | unsigned int n; | |
568 | unsigned long flags; | |
569 | ||
570 | for (n = 0; n < insn->n; n++) { | |
571 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
572 | data[n] = dio200_subdev_8254_read_chan(dev, s, chan); | |
573 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
574 | } | |
575 | return insn->n; | |
576 | } | |
577 | ||
42c6767b HS |
578 | static int dio200_subdev_8254_write(struct comedi_device *dev, |
579 | struct comedi_subdevice *s, | |
580 | struct comedi_insn *insn, | |
581 | unsigned int *data) | |
7ff7e4c2 IA |
582 | { |
583 | struct dio200_subdev_8254 *subpriv = s->private; | |
584 | int chan = CR_CHAN(insn->chanspec); | |
585 | unsigned int n; | |
586 | unsigned long flags; | |
587 | ||
588 | for (n = 0; n < insn->n; n++) { | |
589 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
590 | dio200_subdev_8254_write_chan(dev, s, chan, data[n]); | |
591 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
592 | } | |
593 | return insn->n; | |
594 | } | |
595 | ||
42c6767b HS |
596 | static int dio200_subdev_8254_set_gate_src(struct comedi_device *dev, |
597 | struct comedi_subdevice *s, | |
598 | unsigned int counter_number, | |
599 | unsigned int gate_src) | |
7ff7e4c2 | 600 | { |
058543b7 | 601 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
602 | struct dio200_subdev_8254 *subpriv = s->private; |
603 | unsigned char byte; | |
604 | ||
f6ce0950 | 605 | if (!board->has_clk_gat_sce) |
7ff7e4c2 IA |
606 | return -1; |
607 | if (counter_number > 2) | |
608 | return -1; | |
c1b0cccc | 609 | if (gate_src > (board->is_pcie ? 31 : 7)) |
7ff7e4c2 IA |
610 | return -1; |
611 | ||
612 | subpriv->gate_src[counter_number] = gate_src; | |
613 | byte = gat_sce(subpriv->which, counter_number, gate_src); | |
614 | dio200_write8(dev, subpriv->gat_sce_ofs, byte); | |
615 | ||
616 | return 0; | |
617 | } | |
618 | ||
42c6767b HS |
619 | static int dio200_subdev_8254_get_gate_src(struct comedi_device *dev, |
620 | struct comedi_subdevice *s, | |
621 | unsigned int counter_number) | |
7ff7e4c2 | 622 | { |
058543b7 | 623 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
624 | struct dio200_subdev_8254 *subpriv = s->private; |
625 | ||
f6ce0950 | 626 | if (!board->has_clk_gat_sce) |
7ff7e4c2 IA |
627 | return -1; |
628 | if (counter_number > 2) | |
629 | return -1; | |
630 | ||
631 | return subpriv->gate_src[counter_number]; | |
632 | } | |
633 | ||
42c6767b HS |
634 | static int dio200_subdev_8254_set_clock_src(struct comedi_device *dev, |
635 | struct comedi_subdevice *s, | |
636 | unsigned int counter_number, | |
637 | unsigned int clock_src) | |
7ff7e4c2 | 638 | { |
058543b7 | 639 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
640 | struct dio200_subdev_8254 *subpriv = s->private; |
641 | unsigned char byte; | |
642 | ||
f6ce0950 | 643 | if (!board->has_clk_gat_sce) |
7ff7e4c2 IA |
644 | return -1; |
645 | if (counter_number > 2) | |
646 | return -1; | |
c1b0cccc | 647 | if (clock_src > (board->is_pcie ? 31 : 7)) |
7ff7e4c2 IA |
648 | return -1; |
649 | ||
650 | subpriv->clock_src[counter_number] = clock_src; | |
651 | byte = clk_sce(subpriv->which, counter_number, clock_src); | |
652 | dio200_write8(dev, subpriv->clk_sce_ofs, byte); | |
653 | ||
654 | return 0; | |
655 | } | |
656 | ||
42c6767b HS |
657 | static int dio200_subdev_8254_get_clock_src(struct comedi_device *dev, |
658 | struct comedi_subdevice *s, | |
659 | unsigned int counter_number, | |
660 | unsigned int *period_ns) | |
7ff7e4c2 | 661 | { |
058543b7 | 662 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
663 | struct dio200_subdev_8254 *subpriv = s->private; |
664 | unsigned clock_src; | |
665 | ||
f6ce0950 | 666 | if (!board->has_clk_gat_sce) |
7ff7e4c2 IA |
667 | return -1; |
668 | if (counter_number > 2) | |
669 | return -1; | |
670 | ||
671 | clock_src = subpriv->clock_src[counter_number]; | |
672 | *period_ns = clock_period[clock_src]; | |
673 | return clock_src; | |
674 | } | |
675 | ||
42c6767b HS |
676 | static int dio200_subdev_8254_config(struct comedi_device *dev, |
677 | struct comedi_subdevice *s, | |
678 | struct comedi_insn *insn, | |
679 | unsigned int *data) | |
7ff7e4c2 IA |
680 | { |
681 | struct dio200_subdev_8254 *subpriv = s->private; | |
682 | int ret = 0; | |
683 | int chan = CR_CHAN(insn->chanspec); | |
684 | unsigned long flags; | |
685 | ||
686 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
687 | switch (data[0]) { | |
688 | case INSN_CONFIG_SET_COUNTER_MODE: | |
6e2954e8 | 689 | if (data[1] > (I8254_MODE5 | I8254_BCD)) |
7ff7e4c2 IA |
690 | ret = -EINVAL; |
691 | else | |
692 | dio200_subdev_8254_set_mode(dev, s, chan, data[1]); | |
693 | break; | |
694 | case INSN_CONFIG_8254_READ_STATUS: | |
695 | data[1] = dio200_subdev_8254_status(dev, s, chan); | |
696 | break; | |
697 | case INSN_CONFIG_SET_GATE_SRC: | |
698 | ret = dio200_subdev_8254_set_gate_src(dev, s, chan, data[2]); | |
699 | if (ret < 0) | |
700 | ret = -EINVAL; | |
701 | break; | |
702 | case INSN_CONFIG_GET_GATE_SRC: | |
703 | ret = dio200_subdev_8254_get_gate_src(dev, s, chan); | |
704 | if (ret < 0) { | |
705 | ret = -EINVAL; | |
706 | break; | |
707 | } | |
708 | data[2] = ret; | |
709 | break; | |
710 | case INSN_CONFIG_SET_CLOCK_SRC: | |
711 | ret = dio200_subdev_8254_set_clock_src(dev, s, chan, data[1]); | |
712 | if (ret < 0) | |
713 | ret = -EINVAL; | |
714 | break; | |
715 | case INSN_CONFIG_GET_CLOCK_SRC: | |
716 | ret = dio200_subdev_8254_get_clock_src(dev, s, chan, &data[2]); | |
717 | if (ret < 0) { | |
718 | ret = -EINVAL; | |
719 | break; | |
720 | } | |
721 | data[1] = ret; | |
722 | break; | |
723 | default: | |
724 | ret = -EINVAL; | |
725 | break; | |
726 | } | |
727 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
728 | return ret < 0 ? ret : insn->n; | |
729 | } | |
730 | ||
42c6767b HS |
731 | static int dio200_subdev_8254_init(struct comedi_device *dev, |
732 | struct comedi_subdevice *s, | |
733 | unsigned int offset) | |
7ff7e4c2 | 734 | { |
058543b7 | 735 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 IA |
736 | struct dio200_subdev_8254 *subpriv; |
737 | unsigned int chan; | |
738 | ||
0480bcb9 | 739 | subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); |
7ff7e4c2 IA |
740 | if (!subpriv) |
741 | return -ENOMEM; | |
742 | ||
7ff7e4c2 IA |
743 | s->type = COMEDI_SUBD_COUNTER; |
744 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
745 | s->n_chan = 3; | |
746 | s->maxdata = 0xFFFF; | |
747 | s->insn_read = dio200_subdev_8254_read; | |
748 | s->insn_write = dio200_subdev_8254_write; | |
749 | s->insn_config = dio200_subdev_8254_config; | |
750 | ||
751 | spin_lock_init(&subpriv->spinlock); | |
752 | subpriv->ofs = offset; | |
f6ce0950 | 753 | if (board->has_clk_gat_sce) { |
7ff7e4c2 IA |
754 | /* Derive CLK_SCE and GAT_SCE register offsets from |
755 | * 8254 offset. */ | |
756 | subpriv->clk_sce_ofs = DIO200_XCLK_SCE + (offset >> 3); | |
757 | subpriv->gat_sce_ofs = DIO200_XGAT_SCE + (offset >> 3); | |
758 | subpriv->which = (offset >> 2) & 1; | |
759 | } | |
760 | ||
761 | /* Initialize channels. */ | |
762 | for (chan = 0; chan < 3; chan++) { | |
763 | dio200_subdev_8254_set_mode(dev, s, chan, | |
764 | I8254_MODE0 | I8254_BINARY); | |
f6ce0950 | 765 | if (board->has_clk_gat_sce) { |
7ff7e4c2 IA |
766 | /* Gate source 0 is VCC (logic 1). */ |
767 | dio200_subdev_8254_set_gate_src(dev, s, chan, 0); | |
768 | /* Clock source 0 is the dedicated clock input. */ | |
769 | dio200_subdev_8254_set_clock_src(dev, s, chan, 0); | |
770 | } | |
771 | } | |
772 | ||
773 | return 0; | |
774 | } | |
775 | ||
7ff7e4c2 IA |
776 | static void dio200_subdev_8255_set_dir(struct comedi_device *dev, |
777 | struct comedi_subdevice *s) | |
778 | { | |
779 | struct dio200_subdev_8255 *subpriv = s->private; | |
780 | int config; | |
781 | ||
f0162091 | 782 | config = I8255_CTRL_CW; |
7ff7e4c2 IA |
783 | /* 1 in io_bits indicates output, 1 in config indicates input */ |
784 | if (!(s->io_bits & 0x0000ff)) | |
f0162091 | 785 | config |= I8255_CTRL_A_IO; |
7ff7e4c2 | 786 | if (!(s->io_bits & 0x00ff00)) |
f0162091 | 787 | config |= I8255_CTRL_B_IO; |
7ff7e4c2 | 788 | if (!(s->io_bits & 0x0f0000)) |
f0162091 | 789 | config |= I8255_CTRL_C_LO_IO; |
7ff7e4c2 | 790 | if (!(s->io_bits & 0xf00000)) |
f0162091 HS |
791 | config |= I8255_CTRL_C_HI_IO; |
792 | dio200_write8(dev, subpriv->ofs + I8255_CTRL_REG, config); | |
7ff7e4c2 IA |
793 | } |
794 | ||
7ff7e4c2 IA |
795 | static int dio200_subdev_8255_bits(struct comedi_device *dev, |
796 | struct comedi_subdevice *s, | |
b3ff824a HS |
797 | struct comedi_insn *insn, |
798 | unsigned int *data) | |
7ff7e4c2 IA |
799 | { |
800 | struct dio200_subdev_8255 *subpriv = s->private; | |
b3ff824a HS |
801 | unsigned int mask; |
802 | unsigned int val; | |
7ff7e4c2 | 803 | |
b3ff824a HS |
804 | mask = comedi_dio_update_state(s, data); |
805 | if (mask) { | |
806 | if (mask & 0xff) | |
f0162091 HS |
807 | dio200_write8(dev, subpriv->ofs + I8255_DATA_A_REG, |
808 | s->state & 0xff); | |
b3ff824a | 809 | if (mask & 0xff00) |
f0162091 | 810 | dio200_write8(dev, subpriv->ofs + I8255_DATA_B_REG, |
7ff7e4c2 | 811 | (s->state >> 8) & 0xff); |
b3ff824a | 812 | if (mask & 0xff0000) |
f0162091 | 813 | dio200_write8(dev, subpriv->ofs + I8255_DATA_C_REG, |
7ff7e4c2 IA |
814 | (s->state >> 16) & 0xff); |
815 | } | |
b3ff824a | 816 | |
f0162091 HS |
817 | val = dio200_read8(dev, subpriv->ofs + I8255_DATA_A_REG); |
818 | val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_B_REG) << 8; | |
819 | val |= dio200_read8(dev, subpriv->ofs + I8255_DATA_C_REG) << 16; | |
b3ff824a HS |
820 | |
821 | data[1] = val; | |
822 | ||
823 | return insn->n; | |
7ff7e4c2 IA |
824 | } |
825 | ||
7ff7e4c2 IA |
826 | static int dio200_subdev_8255_config(struct comedi_device *dev, |
827 | struct comedi_subdevice *s, | |
828 | struct comedi_insn *insn, | |
829 | unsigned int *data) | |
830 | { | |
5dacadcc | 831 | unsigned int chan = CR_CHAN(insn->chanspec); |
7ff7e4c2 | 832 | unsigned int mask; |
5dacadcc HS |
833 | int ret; |
834 | ||
835 | if (chan < 8) | |
836 | mask = 0x0000ff; | |
837 | else if (chan < 16) | |
838 | mask = 0x00ff00; | |
839 | else if (chan < 20) | |
840 | mask = 0x0f0000; | |
7ff7e4c2 | 841 | else |
5dacadcc HS |
842 | mask = 0xf00000; |
843 | ||
844 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); | |
845 | if (ret) | |
846 | return ret; | |
847 | ||
7ff7e4c2 | 848 | dio200_subdev_8255_set_dir(dev, s); |
5dacadcc HS |
849 | |
850 | return insn->n; | |
7ff7e4c2 IA |
851 | } |
852 | ||
7ff7e4c2 IA |
853 | static int dio200_subdev_8255_init(struct comedi_device *dev, |
854 | struct comedi_subdevice *s, | |
855 | unsigned int offset) | |
856 | { | |
857 | struct dio200_subdev_8255 *subpriv; | |
858 | ||
0480bcb9 | 859 | subpriv = comedi_alloc_spriv(s, sizeof(*subpriv)); |
7ff7e4c2 IA |
860 | if (!subpriv) |
861 | return -ENOMEM; | |
588ba6dc | 862 | |
7ff7e4c2 | 863 | subpriv->ofs = offset; |
588ba6dc | 864 | |
7ff7e4c2 IA |
865 | s->type = COMEDI_SUBD_DIO; |
866 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
867 | s->n_chan = 24; | |
868 | s->range_table = &range_digital; | |
869 | s->maxdata = 1; | |
870 | s->insn_bits = dio200_subdev_8255_bits; | |
871 | s->insn_config = dio200_subdev_8255_config; | |
7ff7e4c2 IA |
872 | dio200_subdev_8255_set_dir(dev, s); |
873 | return 0; | |
874 | } | |
875 | ||
7ff7e4c2 IA |
876 | static int dio200_subdev_timer_read(struct comedi_device *dev, |
877 | struct comedi_subdevice *s, | |
878 | struct comedi_insn *insn, | |
879 | unsigned int *data) | |
880 | { | |
881 | unsigned int n; | |
882 | ||
883 | for (n = 0; n < insn->n; n++) | |
884 | data[n] = dio200_read32(dev, DIO200_TS_COUNT); | |
885 | return n; | |
886 | } | |
887 | ||
7ff7e4c2 IA |
888 | static void dio200_subdev_timer_reset(struct comedi_device *dev, |
889 | struct comedi_subdevice *s) | |
890 | { | |
891 | unsigned int clock; | |
892 | ||
893 | clock = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; | |
894 | dio200_write32(dev, DIO200_TS_CONFIG, clock | TS_CONFIG_RESET); | |
895 | dio200_write32(dev, DIO200_TS_CONFIG, clock); | |
896 | } | |
897 | ||
7ff7e4c2 IA |
898 | static void dio200_subdev_timer_get_clock_src(struct comedi_device *dev, |
899 | struct comedi_subdevice *s, | |
900 | unsigned int *src, | |
901 | unsigned int *period) | |
902 | { | |
903 | unsigned int clk; | |
904 | ||
905 | clk = dio200_read32(dev, DIO200_TS_CONFIG) & TS_CONFIG_CLK_SRC_MASK; | |
906 | *src = clk; | |
907 | *period = (clk < ARRAY_SIZE(ts_clock_period)) ? | |
908 | ts_clock_period[clk] : 0; | |
909 | } | |
910 | ||
7ff7e4c2 IA |
911 | static int dio200_subdev_timer_set_clock_src(struct comedi_device *dev, |
912 | struct comedi_subdevice *s, | |
913 | unsigned int src) | |
914 | { | |
915 | if (src > TS_CONFIG_MAX_CLK_SRC) | |
916 | return -EINVAL; | |
917 | dio200_write32(dev, DIO200_TS_CONFIG, src); | |
918 | return 0; | |
919 | } | |
920 | ||
7ff7e4c2 IA |
921 | static int dio200_subdev_timer_config(struct comedi_device *dev, |
922 | struct comedi_subdevice *s, | |
923 | struct comedi_insn *insn, | |
924 | unsigned int *data) | |
925 | { | |
926 | int ret = 0; | |
927 | ||
928 | switch (data[0]) { | |
929 | case INSN_CONFIG_RESET: | |
930 | dio200_subdev_timer_reset(dev, s); | |
931 | break; | |
932 | case INSN_CONFIG_SET_CLOCK_SRC: | |
933 | ret = dio200_subdev_timer_set_clock_src(dev, s, data[1]); | |
934 | if (ret < 0) | |
935 | ret = -EINVAL; | |
936 | break; | |
937 | case INSN_CONFIG_GET_CLOCK_SRC: | |
938 | dio200_subdev_timer_get_clock_src(dev, s, &data[1], &data[2]); | |
939 | break; | |
940 | default: | |
941 | ret = -EINVAL; | |
942 | break; | |
943 | } | |
944 | return ret < 0 ? ret : insn->n; | |
945 | } | |
946 | ||
7ff7e4c2 IA |
947 | void amplc_dio200_set_enhance(struct comedi_device *dev, unsigned char val) |
948 | { | |
949 | dio200_write8(dev, DIO200_ENHANCE, val); | |
950 | } | |
951 | EXPORT_SYMBOL_GPL(amplc_dio200_set_enhance); | |
952 | ||
953 | int amplc_dio200_common_attach(struct comedi_device *dev, unsigned int irq, | |
954 | unsigned long req_irq_flags) | |
955 | { | |
058543b7 | 956 | const struct dio200_board *board = dev->board_ptr; |
7ff7e4c2 | 957 | struct comedi_subdevice *s; |
7ff7e4c2 IA |
958 | unsigned int n; |
959 | int ret; | |
960 | ||
f6ce0950 | 961 | ret = comedi_alloc_subdevices(dev, board->n_subdevs); |
7ff7e4c2 IA |
962 | if (ret) |
963 | return ret; | |
964 | ||
965 | for (n = 0; n < dev->n_subdevices; n++) { | |
966 | s = &dev->subdevices[n]; | |
f6ce0950 | 967 | switch (board->sdtype[n]) { |
7ff7e4c2 IA |
968 | case sd_8254: |
969 | /* counter subdevice (8254) */ | |
970 | ret = dio200_subdev_8254_init(dev, s, | |
f6ce0950 | 971 | board->sdinfo[n]); |
7ff7e4c2 IA |
972 | if (ret < 0) |
973 | return ret; | |
974 | break; | |
975 | case sd_8255: | |
976 | /* digital i/o subdevice (8255) */ | |
977 | ret = dio200_subdev_8255_init(dev, s, | |
f6ce0950 | 978 | board->sdinfo[n]); |
7ff7e4c2 IA |
979 | if (ret < 0) |
980 | return ret; | |
981 | break; | |
982 | case sd_intr: | |
983 | /* 'INTERRUPT' subdevice */ | |
76212bf3 | 984 | if (irq && !dev->read_subdev) { |
7ff7e4c2 IA |
985 | ret = dio200_subdev_intr_init(dev, s, |
986 | DIO200_INT_SCE, | |
f6ce0950 | 987 | board->sdinfo[n]); |
7ff7e4c2 IA |
988 | if (ret < 0) |
989 | return ret; | |
76212bf3 | 990 | dev->read_subdev = s; |
7ff7e4c2 IA |
991 | } else { |
992 | s->type = COMEDI_SUBD_UNUSED; | |
993 | } | |
994 | break; | |
995 | case sd_timer: | |
294de579 HS |
996 | s->type = COMEDI_SUBD_TIMER; |
997 | s->subdev_flags = SDF_READABLE | SDF_LSAMPL; | |
998 | s->n_chan = 1; | |
999 | s->maxdata = 0xffffffff; | |
1000 | s->insn_read = dio200_subdev_timer_read; | |
1001 | s->insn_config = dio200_subdev_timer_config; | |
7ff7e4c2 IA |
1002 | break; |
1003 | default: | |
1004 | s->type = COMEDI_SUBD_UNUSED; | |
1005 | break; | |
1006 | } | |
1007 | } | |
76212bf3 HS |
1008 | |
1009 | if (irq && dev->read_subdev) { | |
7ff7e4c2 IA |
1010 | if (request_irq(irq, dio200_interrupt, req_irq_flags, |
1011 | dev->board_name, dev) >= 0) { | |
1012 | dev->irq = irq; | |
1013 | } else { | |
1014 | dev_warn(dev->class_dev, | |
1015 | "warning! irq %u unavailable!\n", irq); | |
1016 | } | |
1017 | } | |
c93999c2 | 1018 | |
7ff7e4c2 IA |
1019 | return 0; |
1020 | } | |
1021 | EXPORT_SYMBOL_GPL(amplc_dio200_common_attach); | |
1022 | ||
7ff7e4c2 IA |
1023 | static int __init amplc_dio200_common_init(void) |
1024 | { | |
1025 | return 0; | |
1026 | } | |
1027 | module_init(amplc_dio200_common_init); | |
1028 | ||
1029 | static void __exit amplc_dio200_common_exit(void) | |
1030 | { | |
1031 | } | |
1032 | module_exit(amplc_dio200_common_exit); | |
1033 | ||
1034 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
1035 | MODULE_DESCRIPTION("Comedi helper for amplc_dio200 and amplc_dio200_pci"); | |
1036 | MODULE_LICENSE("GPL"); |