comedi_parport.c (7975B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * comedi_parport.c 4 * Comedi driver for standard parallel port 5 * 6 * For more information see: 7 * http://retired.beyondlogic.org/spp/parallel.htm 8 * 9 * COMEDI - Linux Control and Measurement Device Interface 10 * Copyright (C) 1998,2001 David A. Schleef <ds@schleef.org> 11 */ 12 13/* 14 * Driver: comedi_parport 15 * Description: Standard PC parallel port 16 * Author: ds 17 * Status: works in immediate mode 18 * Devices: [standard] parallel port (comedi_parport) 19 * Updated: Tue, 30 Apr 2002 21:11:45 -0700 20 * 21 * A cheap and easy way to get a few more digital I/O lines. Steal 22 * additional parallel ports from old computers or your neighbors' 23 * computers. 24 * 25 * Option list: 26 * 0: I/O port base for the parallel port. 27 * 1: IRQ (optional) 28 * 29 * Parallel Port Lines: 30 * 31 * pin subdev chan type name 32 * ----- ------ ---- ---- -------------- 33 * 1 2 0 DO strobe 34 * 2 0 0 DIO data 0 35 * 3 0 1 DIO data 1 36 * 4 0 2 DIO data 2 37 * 5 0 3 DIO data 3 38 * 6 0 4 DIO data 4 39 * 7 0 5 DIO data 5 40 * 8 0 6 DIO data 6 41 * 9 0 7 DIO data 7 42 * 10 1 3 DI ack 43 * 11 1 4 DI busy 44 * 12 1 2 DI paper out 45 * 13 1 1 DI select in 46 * 14 2 1 DO auto LF 47 * 15 1 0 DI error 48 * 16 2 2 DO init 49 * 17 2 3 DO select printer 50 * 18-25 ground 51 * 52 * When an IRQ is configured subdevice 3 pretends to be a digital 53 * input subdevice, but it always returns 0 when read. However, if 54 * you run a command with scan_begin_src=TRIG_EXT, it uses pin 10 55 * as a external trigger, which can be used to wake up tasks. 56 */ 57 58#include <linux/module.h> 59#include <linux/interrupt.h> 60#include <linux/comedi/comedidev.h> 61 62/* 63 * Register map 64 */ 65#define PARPORT_DATA_REG 0x00 66#define PARPORT_STATUS_REG 0x01 67#define PARPORT_CTRL_REG 0x02 68#define PARPORT_CTRL_IRQ_ENA BIT(4) 69#define PARPORT_CTRL_BIDIR_ENA BIT(5) 70 71static int parport_data_reg_insn_bits(struct comedi_device *dev, 72 struct comedi_subdevice *s, 73 struct comedi_insn *insn, 74 unsigned int *data) 75{ 76 if (comedi_dio_update_state(s, data)) 77 outb(s->state, dev->iobase + PARPORT_DATA_REG); 78 79 data[1] = inb(dev->iobase + PARPORT_DATA_REG); 80 81 return insn->n; 82} 83 84static int parport_data_reg_insn_config(struct comedi_device *dev, 85 struct comedi_subdevice *s, 86 struct comedi_insn *insn, 87 unsigned int *data) 88{ 89 unsigned int ctrl; 90 int ret; 91 92 ret = comedi_dio_insn_config(dev, s, insn, data, 0xff); 93 if (ret) 94 return ret; 95 96 ctrl = inb(dev->iobase + PARPORT_CTRL_REG); 97 if (s->io_bits) 98 ctrl &= ~PARPORT_CTRL_BIDIR_ENA; 99 else 100 ctrl |= PARPORT_CTRL_BIDIR_ENA; 101 outb(ctrl, dev->iobase + PARPORT_CTRL_REG); 102 103 return insn->n; 104} 105 106static int parport_status_reg_insn_bits(struct comedi_device *dev, 107 struct comedi_subdevice *s, 108 struct comedi_insn *insn, 109 unsigned int *data) 110{ 111 data[1] = inb(dev->iobase + PARPORT_STATUS_REG) >> 3; 112 113 return insn->n; 114} 115 116static int parport_ctrl_reg_insn_bits(struct comedi_device *dev, 117 struct comedi_subdevice *s, 118 struct comedi_insn *insn, 119 unsigned int *data) 120{ 121 unsigned int ctrl; 122 123 if (comedi_dio_update_state(s, data)) { 124 ctrl = inb(dev->iobase + PARPORT_CTRL_REG); 125 ctrl &= (PARPORT_CTRL_IRQ_ENA | PARPORT_CTRL_BIDIR_ENA); 126 ctrl |= s->state; 127 outb(ctrl, dev->iobase + PARPORT_CTRL_REG); 128 } 129 130 data[1] = s->state; 131 132 return insn->n; 133} 134 135static int parport_intr_insn_bits(struct comedi_device *dev, 136 struct comedi_subdevice *s, 137 struct comedi_insn *insn, 138 unsigned int *data) 139{ 140 data[1] = 0; 141 return insn->n; 142} 143 144static int parport_intr_cmdtest(struct comedi_device *dev, 145 struct comedi_subdevice *s, 146 struct comedi_cmd *cmd) 147{ 148 int err = 0; 149 150 /* Step 1 : check if triggers are trivially valid */ 151 152 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 153 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); 154 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); 155 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 156 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE); 157 158 if (err) 159 return 1; 160 161 /* Step 2a : make sure trigger sources are unique */ 162 /* Step 2b : and mutually compatible */ 163 164 /* Step 3: check if arguments are trivially valid */ 165 166 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 167 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 168 err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); 169 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 170 cmd->chanlist_len); 171 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 172 173 if (err) 174 return 3; 175 176 /* Step 4: fix up any arguments */ 177 178 /* Step 5: check channel list if it exists */ 179 180 return 0; 181} 182 183static int parport_intr_cmd(struct comedi_device *dev, 184 struct comedi_subdevice *s) 185{ 186 unsigned int ctrl; 187 188 ctrl = inb(dev->iobase + PARPORT_CTRL_REG); 189 ctrl |= PARPORT_CTRL_IRQ_ENA; 190 outb(ctrl, dev->iobase + PARPORT_CTRL_REG); 191 192 return 0; 193} 194 195static int parport_intr_cancel(struct comedi_device *dev, 196 struct comedi_subdevice *s) 197{ 198 unsigned int ctrl; 199 200 ctrl = inb(dev->iobase + PARPORT_CTRL_REG); 201 ctrl &= ~PARPORT_CTRL_IRQ_ENA; 202 outb(ctrl, dev->iobase + PARPORT_CTRL_REG); 203 204 return 0; 205} 206 207static irqreturn_t parport_interrupt(int irq, void *d) 208{ 209 struct comedi_device *dev = d; 210 struct comedi_subdevice *s = dev->read_subdev; 211 unsigned int ctrl; 212 unsigned short val = 0; 213 214 ctrl = inb(dev->iobase + PARPORT_CTRL_REG); 215 if (!(ctrl & PARPORT_CTRL_IRQ_ENA)) 216 return IRQ_NONE; 217 218 comedi_buf_write_samples(s, &val, 1); 219 comedi_handle_events(dev, s); 220 221 return IRQ_HANDLED; 222} 223 224static int parport_attach(struct comedi_device *dev, 225 struct comedi_devconfig *it) 226{ 227 struct comedi_subdevice *s; 228 int ret; 229 230 ret = comedi_request_region(dev, it->options[0], 0x03); 231 if (ret) 232 return ret; 233 234 if (it->options[1]) { 235 ret = request_irq(it->options[1], parport_interrupt, 0, 236 dev->board_name, dev); 237 if (ret == 0) 238 dev->irq = it->options[1]; 239 } 240 241 ret = comedi_alloc_subdevices(dev, dev->irq ? 4 : 3); 242 if (ret) 243 return ret; 244 245 /* Digial I/O subdevice - Parallel port DATA register */ 246 s = &dev->subdevices[0]; 247 s->type = COMEDI_SUBD_DIO; 248 s->subdev_flags = SDF_READABLE | SDF_WRITABLE; 249 s->n_chan = 8; 250 s->maxdata = 1; 251 s->range_table = &range_digital; 252 s->insn_bits = parport_data_reg_insn_bits; 253 s->insn_config = parport_data_reg_insn_config; 254 255 /* Digial Input subdevice - Parallel port STATUS register */ 256 s = &dev->subdevices[1]; 257 s->type = COMEDI_SUBD_DI; 258 s->subdev_flags = SDF_READABLE; 259 s->n_chan = 5; 260 s->maxdata = 1; 261 s->range_table = &range_digital; 262 s->insn_bits = parport_status_reg_insn_bits; 263 264 /* Digial Output subdevice - Parallel port CONTROL register */ 265 s = &dev->subdevices[2]; 266 s->type = COMEDI_SUBD_DO; 267 s->subdev_flags = SDF_WRITABLE; 268 s->n_chan = 4; 269 s->maxdata = 1; 270 s->range_table = &range_digital; 271 s->insn_bits = parport_ctrl_reg_insn_bits; 272 273 if (dev->irq) { 274 /* Digial Input subdevice - Interrupt support */ 275 s = &dev->subdevices[3]; 276 dev->read_subdev = s; 277 s->type = COMEDI_SUBD_DI; 278 s->subdev_flags = SDF_READABLE | SDF_CMD_READ; 279 s->n_chan = 1; 280 s->maxdata = 1; 281 s->range_table = &range_digital; 282 s->insn_bits = parport_intr_insn_bits; 283 s->len_chanlist = 1; 284 s->do_cmdtest = parport_intr_cmdtest; 285 s->do_cmd = parport_intr_cmd; 286 s->cancel = parport_intr_cancel; 287 } 288 289 outb(0, dev->iobase + PARPORT_DATA_REG); 290 outb(0, dev->iobase + PARPORT_CTRL_REG); 291 292 return 0; 293} 294 295static struct comedi_driver parport_driver = { 296 .driver_name = "comedi_parport", 297 .module = THIS_MODULE, 298 .attach = parport_attach, 299 .detach = comedi_legacy_detach, 300}; 301module_comedi_driver(parport_driver); 302 303MODULE_AUTHOR("Comedi https://www.comedi.org"); 304MODULE_DESCRIPTION("Comedi: Standard parallel port driver"); 305MODULE_LICENSE("GPL");