staging: comedi: addi_apci_1564: clean up apci1564_interrupt()
[pandora-kernel.git] / drivers / staging / comedi / drivers / addi_apci_1564.c
1 #include <linux/module.h>
2 #include <linux/pci.h>
3
4 #include "../comedidev.h"
5 #include "comedi_fc.h"
6 #include "amcc_s5933.h"
7 #include "addi_watchdog.h"
8
9 #include "addi-data/addi_common.h"
10
11 struct apci1564_private {
12         unsigned int amcc_iobase;       /* base of AMCC I/O registers */
13         unsigned int mode1;             /* riding-edge/high level channels */
14         unsigned int mode2;             /* falling-edge/low level channels */
15         unsigned int ctrl;              /* interrupt mode OR (edge) . AND (level) */
16         unsigned int do_int_type;
17         unsigned char timer_select_mode;
18         unsigned char mode_select_register;
19         struct task_struct *tsk_current;
20 };
21
22 #include "addi-data/hwdrv_apci1564.c"
23
24 static int apci1564_reset(struct comedi_device *dev)
25 {
26         struct apci1564_private *devpriv = dev->private;
27
28         devpriv->do_int_type = 0;
29
30         /* Disable the input interrupts and reset status register */
31         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
32         inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
33         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
34         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
35
36         /* Reset the output channels and disable interrupts */
37         outl(0x0, devpriv->amcc_iobase + APCI1564_DO_REG);
38         outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
39
40         /* Reset the watchdog registers */
41         addi_watchdog_reset(devpriv->amcc_iobase + APCI1564_WDOG_REG);
42
43         /* Reset the timer registers */
44         outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
45         outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_RELOAD_REG);
46
47         /* Reset the counter registers */
48         outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER1));
49         outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER2));
50         outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER3));
51         outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(APCI1564_COUNTER4));
52
53         return 0;
54 }
55
56 static irqreturn_t apci1564_interrupt(int irq, void *d)
57 {
58         struct comedi_device *dev = d;
59         struct apci1564_private *devpriv = dev->private;
60         struct comedi_subdevice *s = dev->read_subdev;
61         unsigned int status;
62         unsigned int ctrl;
63         unsigned int chan;
64
65         /* check interrupt is from this device */
66         if ((inl(devpriv->amcc_iobase + AMCC_OP_REG_INTCSR) &
67              INTCSR_INTR_ASSERTED) == 0)
68                 return IRQ_NONE;
69
70         status = inl(devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
71         if (status & APCI1564_DI_INT_ENABLE) {
72                 /* disable the interrupt */
73                 outl(status & APCI1564_DI_INT_DISABLE,
74                      devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
75
76                 s->state = inl(dev->iobase + APCI1564_DI_INT_STATUS_REG)
77                                & 0xffff;
78                 comedi_buf_put(s, s->state);
79                 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
80                 comedi_event(dev, s);
81
82                 /* enable the interrupt */
83                 outl(status, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
84         }
85
86         status = inl(devpriv->amcc_iobase + APCI1564_DO_IRQ_REG);
87         if (status & 0x01) {
88                 /* Check for Digital Output interrupt Type */
89                 /* 1: VCC interrupt                        */
90                 /* 2: CC interrupt                         */
91                 devpriv->do_int_type = inl(devpriv->amcc_iobase +
92                                           APCI1564_DO_INT_STATUS_REG) & 0x3;
93                 /* Disable the  Interrupt */
94                 outl(0x0, devpriv->amcc_iobase + APCI1564_DO_INT_CTRL_REG);
95
96                 /* Sends signal to user space */
97                 send_sig(SIGIO, devpriv->tsk_current, 0);
98         }
99
100         status = inl(devpriv->amcc_iobase + APCI1564_TIMER_IRQ_REG);
101         if (status & 0x01) {
102                 /*  Disable Timer Interrupt */
103                 ctrl = inl(devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
104                 outl(0x0, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
105
106                 /* Send a signal to from kernel to user space */
107                 send_sig(SIGIO, devpriv->tsk_current, 0);
108
109                 /*  Enable Timer Interrupt */
110                 outl(ctrl, devpriv->amcc_iobase + APCI1564_TIMER_CTRL_REG);
111         }
112
113         for (chan = 0; chan < 4; chan++) {
114                 status = inl(dev->iobase + APCI1564_TCW_IRQ_REG(chan));
115                 if (status & 0x01) {
116                         /*  Disable Counter Interrupt */
117                         ctrl = inl(dev->iobase + APCI1564_TCW_CTRL_REG(chan));
118                         outl(0x0, dev->iobase + APCI1564_TCW_CTRL_REG(chan));
119
120                         /* Send a signal to from kernel to user space */
121                         send_sig(SIGIO, devpriv->tsk_current, 0);
122
123                         /*  Enable Counter Interrupt */
124                         outl(ctrl, dev->iobase + APCI1564_TCW_CTRL_REG(chan));
125                 }
126         }
127
128         return IRQ_HANDLED;
129 }
130
131 static int apci1564_di_insn_bits(struct comedi_device *dev,
132                                  struct comedi_subdevice *s,
133                                  struct comedi_insn *insn,
134                                  unsigned int *data)
135 {
136         struct apci1564_private *devpriv = dev->private;
137
138         data[1] = inl(devpriv->amcc_iobase + APCI1564_DI_REG);
139
140         return insn->n;
141 }
142
143 static int apci1564_do_insn_bits(struct comedi_device *dev,
144                                  struct comedi_subdevice *s,
145                                  struct comedi_insn *insn,
146                                  unsigned int *data)
147 {
148         struct apci1564_private *devpriv = dev->private;
149
150         s->state = inl(devpriv->amcc_iobase + APCI1564_DO_REG);
151
152         if (comedi_dio_update_state(s, data))
153                 outl(s->state, devpriv->amcc_iobase + APCI1564_DO_REG);
154
155         data[1] = s->state;
156
157         return insn->n;
158 }
159
160 /*
161  * Change-Of-State (COS) interrupt configuration
162  *
163  * Channels 0 to 15 are interruptible. These channels can be configured
164  * to generate interrupts based on AND/OR logic for the desired channels.
165  *
166  *      OR logic
167  *              - reacts to rising or falling edges
168  *              - interrupt is generated when any enabled channel
169  *                meet the desired interrupt condition
170  *
171  *      AND logic
172  *              - reacts to changes in level of the selected inputs
173  *              - interrupt is generated when all enabled channels
174  *                meet the desired interrupt condition
175  *              - after an interrupt, a change in level must occur on
176  *                the selected inputs to release the IRQ logic
177  *
178  * The COS interrupt must be configured before it can be enabled.
179  *
180  *      data[0] : INSN_CONFIG_DIGITAL_TRIG
181  *      data[1] : trigger number (= 0)
182  *      data[2] : configuration operation:
183  *                COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
184  *                COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
185  *                COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
186  *      data[3] : left-shift for data[4] and data[5]
187  *      data[4] : rising-edge/high level channels
188  *      data[5] : falling-edge/low level channels
189  */
190 static int apci1564_cos_insn_config(struct comedi_device *dev,
191                                     struct comedi_subdevice *s,
192                                     struct comedi_insn *insn,
193                                     unsigned int *data)
194 {
195         struct apci1564_private *devpriv = dev->private;
196         unsigned int shift, oldmask;
197
198         switch (data[0]) {
199         case INSN_CONFIG_DIGITAL_TRIG:
200                 if (data[1] != 0)
201                         return -EINVAL;
202                 shift = data[3];
203                 oldmask = (1U << shift) - 1;
204                 switch (data[2]) {
205                 case COMEDI_DIGITAL_TRIG_DISABLE:
206                         devpriv->ctrl = 0;
207                         devpriv->mode1 = 0;
208                         devpriv->mode2 = 0;
209                         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
210                         inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
211                         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
212                         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
213                         break;
214                 case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
215                         if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
216                                               APCI1564_DI_INT_OR)) {
217                                 /* switching to 'OR' mode */
218                                 devpriv->ctrl = APCI1564_DI_INT_ENABLE |
219                                                 APCI1564_DI_INT_OR;
220                                 /* wipe old channels */
221                                 devpriv->mode1 = 0;
222                                 devpriv->mode2 = 0;
223                         } else {
224                                 /* preserve unspecified channels */
225                                 devpriv->mode1 &= oldmask;
226                                 devpriv->mode2 &= oldmask;
227                         }
228                         /* configure specified channels */
229                         devpriv->mode1 |= data[4] << shift;
230                         devpriv->mode2 |= data[5] << shift;
231                         break;
232                 case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
233                         if (devpriv->ctrl != (APCI1564_DI_INT_ENABLE |
234                                               APCI1564_DI_INT_AND)) {
235                                 /* switching to 'AND' mode */
236                                 devpriv->ctrl = APCI1564_DI_INT_ENABLE |
237                                                 APCI1564_DI_INT_AND;
238                                 /* wipe old channels */
239                                 devpriv->mode1 = 0;
240                                 devpriv->mode2 = 0;
241                         } else {
242                                 /* preserve unspecified channels */
243                                 devpriv->mode1 &= oldmask;
244                                 devpriv->mode2 &= oldmask;
245                         }
246                         /* configure specified channels */
247                         devpriv->mode1 |= data[4] << shift;
248                         devpriv->mode2 |= data[5] << shift;
249                         break;
250                 default:
251                         return -EINVAL;
252                 }
253                 break;
254         default:
255                 return -EINVAL;
256         }
257         return insn->n;
258 }
259
260 static int apci1564_cos_insn_bits(struct comedi_device *dev,
261                                   struct comedi_subdevice *s,
262                                   struct comedi_insn *insn,
263                                   unsigned int *data)
264 {
265         data[1] = s->state;
266
267         return 0;
268 }
269
270 static int apci1564_cos_cmdtest(struct comedi_device *dev,
271                                 struct comedi_subdevice *s,
272                                 struct comedi_cmd *cmd)
273 {
274         int err = 0;
275
276         /* Step 1 : check if triggers are trivially valid */
277
278         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
279         err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
280         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
281         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
282         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
283
284         if (err)
285                 return 1;
286
287         /* Step 2a : make sure trigger sources are unique */
288         /* Step 2b : and mutually compatible */
289
290         if (err)
291                 return 2;
292
293         /* Step 3: check if arguments are trivially valid */
294
295         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
296         err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
297         err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
298         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
299         err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
300
301         if (err)
302                 return 3;
303
304         /* step 4: ignored */
305
306         if (err)
307                 return 4;
308
309         return 0;
310 }
311
312 /*
313  * Change-Of-State (COS) 'do_cmd' operation
314  *
315  * Enable the COS interrupt as configured by apci1564_cos_insn_config().
316  */
317 static int apci1564_cos_cmd(struct comedi_device *dev,
318                             struct comedi_subdevice *s)
319 {
320         struct apci1564_private *devpriv = dev->private;
321
322         if (!devpriv->ctrl) {
323                 dev_warn(dev->class_dev,
324                         "Interrupts disabled due to mode configuration!\n");
325                 return -EINVAL;
326         }
327
328         outl(devpriv->mode1, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
329         outl(devpriv->mode2, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
330         outl(devpriv->ctrl, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
331
332         return 0;
333 }
334
335 static int apci1564_cos_cancel(struct comedi_device *dev,
336                                struct comedi_subdevice *s)
337 {
338         struct apci1564_private *devpriv = dev->private;
339
340         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_IRQ_REG);
341         inl(devpriv->amcc_iobase + APCI1564_DI_INT_STATUS_REG);
342         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE1_REG);
343         outl(0x0, devpriv->amcc_iobase + APCI1564_DI_INT_MODE2_REG);
344
345         return 0;
346 }
347
348 static int apci1564_auto_attach(struct comedi_device *dev,
349                                       unsigned long context_unused)
350 {
351         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
352         struct apci1564_private *devpriv;
353         struct comedi_subdevice *s;
354         int ret;
355
356         dev->board_name = dev->driver->driver_name;
357
358         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
359         if (!devpriv)
360                 return -ENOMEM;
361
362         ret = comedi_pci_enable(dev);
363         if (ret)
364                 return ret;
365
366         dev->iobase = pci_resource_start(pcidev, 1);
367         devpriv->amcc_iobase = pci_resource_start(pcidev, 0);
368
369         apci1564_reset(dev);
370
371         if (pcidev->irq > 0) {
372                 ret = request_irq(pcidev->irq, apci1564_interrupt, IRQF_SHARED,
373                                   dev->board_name, dev);
374                 if (ret == 0)
375                         dev->irq = pcidev->irq;
376         }
377
378         ret = comedi_alloc_subdevices(dev, 4);
379         if (ret)
380                 return ret;
381
382         /*  Allocate and Initialise DI Subdevice Structures */
383         s = &dev->subdevices[0];
384         s->type = COMEDI_SUBD_DI;
385         s->subdev_flags = SDF_READABLE;
386         s->n_chan = 32;
387         s->maxdata = 1;
388         s->len_chanlist = 32;
389         s->range_table = &range_digital;
390         s->insn_bits = apci1564_di_insn_bits;
391
392         /*  Allocate and Initialise DO Subdevice Structures */
393         s = &dev->subdevices[1];
394         s->type = COMEDI_SUBD_DO;
395         s->subdev_flags = SDF_WRITEABLE;
396         s->n_chan = 32;
397         s->maxdata = 0xffffffff;
398         s->len_chanlist = 32;
399         s->range_table = &range_digital;
400         s->insn_config = apci1564_do_config;
401         s->insn_bits = apci1564_do_insn_bits;
402         s->insn_read = apci1564_do_read;
403
404         /*  Allocate and Initialise Timer Subdevice Structures */
405         s = &dev->subdevices[2];
406         s->type = COMEDI_SUBD_TIMER;
407         s->subdev_flags = SDF_WRITEABLE;
408         s->n_chan = 1;
409         s->maxdata = 0;
410         s->len_chanlist = 1;
411         s->range_table = &range_digital;
412         s->insn_write = apci1564_timer_write;
413         s->insn_read = apci1564_timer_read;
414         s->insn_config = apci1564_timer_config;
415
416         /* Change-Of-State (COS) interrupt subdevice */
417         s = &dev->subdevices[3];
418         if (dev->irq) {
419                 dev->read_subdev = s;
420                 s->type = COMEDI_SUBD_DI;
421                 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
422                 s->n_chan = 1;
423                 s->maxdata = 1;
424                 s->range_table = &range_digital;
425                 s->len_chanlist = 1;
426                 s->insn_config = apci1564_cos_insn_config;
427                 s->insn_bits = apci1564_cos_insn_bits;
428                 s->do_cmdtest = apci1564_cos_cmdtest;
429                 s->do_cmd = apci1564_cos_cmd;
430                 s->cancel = apci1564_cos_cancel;
431         } else {
432                 s->type = COMEDI_SUBD_UNUSED;
433         }
434
435         return 0;
436 }
437
438 static void apci1564_detach(struct comedi_device *dev)
439 {
440         struct apci1564_private *devpriv = dev->private;
441
442         if (devpriv) {
443                 if (dev->iobase)
444                         apci1564_reset(dev);
445                 if (dev->irq)
446                         free_irq(dev->irq, dev);
447         }
448         comedi_pci_disable(dev);
449 }
450
451 static struct comedi_driver apci1564_driver = {
452         .driver_name    = "addi_apci_1564",
453         .module         = THIS_MODULE,
454         .auto_attach    = apci1564_auto_attach,
455         .detach         = apci1564_detach,
456 };
457
458 static int apci1564_pci_probe(struct pci_dev *dev,
459                               const struct pci_device_id *id)
460 {
461         return comedi_pci_auto_config(dev, &apci1564_driver, id->driver_data);
462 }
463
464 static const struct pci_device_id apci1564_pci_table[] = {
465         { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1006) },
466         { 0 }
467 };
468 MODULE_DEVICE_TABLE(pci, apci1564_pci_table);
469
470 static struct pci_driver apci1564_pci_driver = {
471         .name           = "addi_apci_1564",
472         .id_table       = apci1564_pci_table,
473         .probe          = apci1564_pci_probe,
474         .remove         = comedi_pci_auto_unconfig,
475 };
476 module_comedi_pci_driver(apci1564_driver, apci1564_pci_driver);
477
478 MODULE_AUTHOR("Comedi http://www.comedi.org");
479 MODULE_DESCRIPTION("ADDI-DATA APCI-1564, 32 channel DI / 32 channel DO boards");
480 MODULE_LICENSE("GPL");