• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
61 #include "../comedidev.h"
62 
63 /*
64  * Register map
65  */
66 #define PARPORT_DATA_REG	0x00
67 #define PARPORT_STATUS_REG	0x01
68 #define PARPORT_CTRL_REG	0x02
69 #define PARPORT_CTRL_IRQ_ENA	BIT(4)
70 #define PARPORT_CTRL_BIDIR_ENA	BIT(5)
71 
parport_data_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)72 static int parport_data_reg_insn_bits(struct comedi_device *dev,
73 				      struct comedi_subdevice *s,
74 				      struct comedi_insn *insn,
75 				      unsigned int *data)
76 {
77 	if (comedi_dio_update_state(s, data))
78 		outb(s->state, dev->iobase + PARPORT_DATA_REG);
79 
80 	data[1] = inb(dev->iobase + PARPORT_DATA_REG);
81 
82 	return insn->n;
83 }
84 
parport_data_reg_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)85 static int parport_data_reg_insn_config(struct comedi_device *dev,
86 					struct comedi_subdevice *s,
87 					struct comedi_insn *insn,
88 					unsigned int *data)
89 {
90 	unsigned int ctrl;
91 	int ret;
92 
93 	ret = comedi_dio_insn_config(dev, s, insn, data, 0xff);
94 	if (ret)
95 		return ret;
96 
97 	ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
98 	if (s->io_bits)
99 		ctrl &= ~PARPORT_CTRL_BIDIR_ENA;
100 	else
101 		ctrl |= PARPORT_CTRL_BIDIR_ENA;
102 	outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
103 
104 	return insn->n;
105 }
106 
parport_status_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)107 static int parport_status_reg_insn_bits(struct comedi_device *dev,
108 					struct comedi_subdevice *s,
109 					struct comedi_insn *insn,
110 					unsigned int *data)
111 {
112 	data[1] = inb(dev->iobase + PARPORT_STATUS_REG) >> 3;
113 
114 	return insn->n;
115 }
116 
parport_ctrl_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)117 static int parport_ctrl_reg_insn_bits(struct comedi_device *dev,
118 				      struct comedi_subdevice *s,
119 				      struct comedi_insn *insn,
120 				      unsigned int *data)
121 {
122 	unsigned int ctrl;
123 
124 	if (comedi_dio_update_state(s, data)) {
125 		ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
126 		ctrl &= (PARPORT_CTRL_IRQ_ENA | PARPORT_CTRL_BIDIR_ENA);
127 		ctrl |= s->state;
128 		outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
129 	}
130 
131 	data[1] = s->state;
132 
133 	return insn->n;
134 }
135 
parport_intr_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)136 static int parport_intr_insn_bits(struct comedi_device *dev,
137 				  struct comedi_subdevice *s,
138 				  struct comedi_insn *insn,
139 				  unsigned int *data)
140 {
141 	data[1] = 0;
142 	return insn->n;
143 }
144 
parport_intr_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)145 static int parport_intr_cmdtest(struct comedi_device *dev,
146 				struct comedi_subdevice *s,
147 				struct comedi_cmd *cmd)
148 {
149 	int err = 0;
150 
151 	/* Step 1 : check if triggers are trivially valid */
152 
153 	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
154 	err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
155 	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
156 	err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
157 	err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
158 
159 	if (err)
160 		return 1;
161 
162 	/* Step 2a : make sure trigger sources are unique */
163 	/* Step 2b : and mutually compatible */
164 
165 	/* Step 3: check if arguments are trivially valid */
166 
167 	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
168 	err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
169 	err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
170 	err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
171 					   cmd->chanlist_len);
172 	err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
173 
174 	if (err)
175 		return 3;
176 
177 	/* Step 4: fix up any arguments */
178 
179 	/* Step 5: check channel list if it exists */
180 
181 	return 0;
182 }
183 
parport_intr_cmd(struct comedi_device * dev,struct comedi_subdevice * s)184 static int parport_intr_cmd(struct comedi_device *dev,
185 			    struct comedi_subdevice *s)
186 {
187 	unsigned int ctrl;
188 
189 	ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
190 	ctrl |= PARPORT_CTRL_IRQ_ENA;
191 	outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
192 
193 	return 0;
194 }
195 
parport_intr_cancel(struct comedi_device * dev,struct comedi_subdevice * s)196 static int parport_intr_cancel(struct comedi_device *dev,
197 			       struct comedi_subdevice *s)
198 {
199 	unsigned int ctrl;
200 
201 	ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
202 	ctrl &= ~PARPORT_CTRL_IRQ_ENA;
203 	outb(ctrl, dev->iobase + PARPORT_CTRL_REG);
204 
205 	return 0;
206 }
207 
parport_interrupt(int irq,void * d)208 static irqreturn_t parport_interrupt(int irq, void *d)
209 {
210 	struct comedi_device *dev = d;
211 	struct comedi_subdevice *s = dev->read_subdev;
212 	unsigned int ctrl;
213 	unsigned short val = 0;
214 
215 	ctrl = inb(dev->iobase + PARPORT_CTRL_REG);
216 	if (!(ctrl & PARPORT_CTRL_IRQ_ENA))
217 		return IRQ_NONE;
218 
219 	comedi_buf_write_samples(s, &val, 1);
220 	comedi_handle_events(dev, s);
221 
222 	return IRQ_HANDLED;
223 }
224 
parport_attach(struct comedi_device * dev,struct comedi_devconfig * it)225 static int parport_attach(struct comedi_device *dev,
226 			  struct comedi_devconfig *it)
227 {
228 	struct comedi_subdevice *s;
229 	int ret;
230 
231 	ret = comedi_request_region(dev, it->options[0], 0x03);
232 	if (ret)
233 		return ret;
234 
235 	if (it->options[1]) {
236 		ret = request_irq(it->options[1], parport_interrupt, 0,
237 				  dev->board_name, dev);
238 		if (ret == 0)
239 			dev->irq = it->options[1];
240 	}
241 
242 	ret = comedi_alloc_subdevices(dev, dev->irq ? 4 : 3);
243 	if (ret)
244 		return ret;
245 
246 	/* Digial I/O subdevice - Parallel port DATA register */
247 	s = &dev->subdevices[0];
248 	s->type		= COMEDI_SUBD_DIO;
249 	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
250 	s->n_chan	= 8;
251 	s->maxdata	= 1;
252 	s->range_table	= &range_digital;
253 	s->insn_bits	= parport_data_reg_insn_bits;
254 	s->insn_config	= parport_data_reg_insn_config;
255 
256 	/* Digial Input subdevice - Parallel port STATUS register */
257 	s = &dev->subdevices[1];
258 	s->type		= COMEDI_SUBD_DI;
259 	s->subdev_flags	= SDF_READABLE;
260 	s->n_chan	= 5;
261 	s->maxdata	= 1;
262 	s->range_table	= &range_digital;
263 	s->insn_bits	= parport_status_reg_insn_bits;
264 
265 	/* Digial Output subdevice - Parallel port CONTROL register */
266 	s = &dev->subdevices[2];
267 	s->type		= COMEDI_SUBD_DO;
268 	s->subdev_flags	= SDF_WRITABLE;
269 	s->n_chan	= 4;
270 	s->maxdata	= 1;
271 	s->range_table	= &range_digital;
272 	s->insn_bits	= parport_ctrl_reg_insn_bits;
273 
274 	if (dev->irq) {
275 		/* Digial Input subdevice - Interrupt support */
276 		s = &dev->subdevices[3];
277 		dev->read_subdev = s;
278 		s->type		= COMEDI_SUBD_DI;
279 		s->subdev_flags	= SDF_READABLE | SDF_CMD_READ;
280 		s->n_chan	= 1;
281 		s->maxdata	= 1;
282 		s->range_table	= &range_digital;
283 		s->insn_bits	= parport_intr_insn_bits;
284 		s->len_chanlist	= 1;
285 		s->do_cmdtest	= parport_intr_cmdtest;
286 		s->do_cmd	= parport_intr_cmd;
287 		s->cancel	= parport_intr_cancel;
288 	}
289 
290 	outb(0, dev->iobase + PARPORT_DATA_REG);
291 	outb(0, dev->iobase + PARPORT_CTRL_REG);
292 
293 	return 0;
294 }
295 
296 static struct comedi_driver parport_driver = {
297 	.driver_name	= "comedi_parport",
298 	.module		= THIS_MODULE,
299 	.attach		= parport_attach,
300 	.detach		= comedi_legacy_detach,
301 };
302 module_comedi_driver(parport_driver);
303 
304 MODULE_AUTHOR("Comedi https://www.comedi.org");
305 MODULE_DESCRIPTION("Comedi: Standard parallel port driver");
306 MODULE_LICENSE("GPL");
307