1/* 2 * comedi/drivers/amplc_pc236_common.c 3 * Common support code for "amplc_pc236" and "amplc_pci236". 4 * 5 * Copyright (C) 2002-2014 MEV Ltd. <http://www.mev.co.uk/> 6 * 7 * COMEDI - Linux Control and Measurement Device Interface 8 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 */ 20 21#include <linux/module.h> 22#include <linux/interrupt.h> 23 24#include "../comedidev.h" 25 26#include "amplc_pc236.h" 27#include "comedi_fc.h" 28#include "8255.h" 29 30static void pc236_intr_update(struct comedi_device *dev, bool enable) 31{ 32 const struct pc236_board *thisboard = dev->board_ptr; 33 struct pc236_private *devpriv = dev->private; 34 unsigned long flags; 35 36 spin_lock_irqsave(&dev->spinlock, flags); 37 devpriv->enable_irq = enable; 38 if (thisboard->intr_update_cb) 39 thisboard->intr_update_cb(dev, enable); 40 spin_unlock_irqrestore(&dev->spinlock, flags); 41} 42 43/* 44 * This function is called when an interrupt occurs to check whether 45 * the interrupt has been marked as enabled and was generated by the 46 * board. If so, the function prepares the hardware for the next 47 * interrupt. 48 * Returns false if the interrupt should be ignored. 49 */ 50static bool pc236_intr_check(struct comedi_device *dev) 51{ 52 const struct pc236_board *thisboard = dev->board_ptr; 53 struct pc236_private *devpriv = dev->private; 54 bool retval = false; 55 unsigned long flags; 56 57 spin_lock_irqsave(&dev->spinlock, flags); 58 if (devpriv->enable_irq) { 59 if (thisboard->intr_chk_clr_cb) 60 retval = thisboard->intr_chk_clr_cb(dev); 61 else 62 retval = true; 63 } 64 spin_unlock_irqrestore(&dev->spinlock, flags); 65 66 return retval; 67} 68 69static int pc236_intr_insn(struct comedi_device *dev, 70 struct comedi_subdevice *s, struct comedi_insn *insn, 71 unsigned int *data) 72{ 73 data[1] = 0; 74 return insn->n; 75} 76 77static int pc236_intr_cmdtest(struct comedi_device *dev, 78 struct comedi_subdevice *s, 79 struct comedi_cmd *cmd) 80{ 81 int err = 0; 82 83 /* Step 1 : check if triggers are trivially valid */ 84 85 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); 86 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); 87 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); 88 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 89 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); 90 91 if (err) 92 return 1; 93 94 /* Step 2a : make sure trigger sources are unique */ 95 /* Step 2b : and mutually compatible */ 96 97 /* Step 3: check it arguments are trivially valid */ 98 99 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); 100 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 101 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); 102 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); 103 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); 104 105 if (err) 106 return 3; 107 108 /* Step 4: fix up any arguments */ 109 110 /* Step 5: check channel list if it exists */ 111 112 return 0; 113} 114 115static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 116{ 117 pc236_intr_update(dev, true); 118 119 return 0; 120} 121 122static int pc236_intr_cancel(struct comedi_device *dev, 123 struct comedi_subdevice *s) 124{ 125 pc236_intr_update(dev, false); 126 127 return 0; 128} 129 130static irqreturn_t pc236_interrupt(int irq, void *d) 131{ 132 struct comedi_device *dev = d; 133 struct comedi_subdevice *s = dev->read_subdev; 134 bool handled; 135 136 handled = pc236_intr_check(dev); 137 if (dev->attached && handled) { 138 comedi_buf_put(s, 0); 139 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; 140 comedi_event(dev, s); 141 } 142 return IRQ_RETVAL(handled); 143} 144 145int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase, 146 unsigned int irq, unsigned long req_irq_flags) 147{ 148 struct comedi_subdevice *s; 149 int ret; 150 151 dev->iobase = iobase; 152 153 ret = comedi_alloc_subdevices(dev, 2); 154 if (ret) 155 return ret; 156 157 s = &dev->subdevices[0]; 158 /* digital i/o subdevice (8255) */ 159 ret = subdev_8255_init(dev, s, NULL, 0x00); 160 if (ret) 161 return ret; 162 163 s = &dev->subdevices[1]; 164 dev->read_subdev = s; 165 s->type = COMEDI_SUBD_UNUSED; 166 pc236_intr_update(dev, false); 167 if (irq) { 168 if (request_irq(irq, pc236_interrupt, req_irq_flags, 169 dev->board_name, dev) >= 0) { 170 dev->irq = irq; 171 s->type = COMEDI_SUBD_DI; 172 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 173 s->n_chan = 1; 174 s->maxdata = 1; 175 s->range_table = &range_digital; 176 s->insn_bits = pc236_intr_insn; 177 s->len_chanlist = 1; 178 s->do_cmdtest = pc236_intr_cmdtest; 179 s->do_cmd = pc236_intr_cmd; 180 s->cancel = pc236_intr_cancel; 181 } 182 } 183 184 return 0; 185} 186EXPORT_SYMBOL_GPL(amplc_pc236_common_attach); 187 188static int __init amplc_pc236_common_init(void) 189{ 190 return 0; 191} 192module_init(amplc_pc236_common_init); 193 194static void __exit amplc_pc236_common_exit(void) 195{ 196} 197module_exit(amplc_pc236_common_exit); 198 199 200MODULE_AUTHOR("Comedi http://www.comedi.org"); 201MODULE_DESCRIPTION("Comedi helper for amplc_pc236 and amplc_pci236"); 202MODULE_LICENSE("GPL"); 203