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