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