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