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 
parport_data_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)71 static 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 
parport_data_reg_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)84 static 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 
parport_status_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)106 static 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 
parport_ctrl_reg_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)116 static 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 
parport_intr_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)135 static 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 
parport_intr_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)144 static 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 
parport_intr_cmd(struct comedi_device * dev,struct comedi_subdevice * s)183 static 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 
parport_intr_cancel(struct comedi_device * dev,struct comedi_subdevice * s)195 static 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 
parport_interrupt(int irq,void * d)207 static 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 
parport_attach(struct comedi_device * dev,struct comedi_devconfig * it)224 static 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 
295 static struct comedi_driver parport_driver = {
296 	.driver_name	= "comedi_parport",
297 	.module		= THIS_MODULE,
298 	.attach		= parport_attach,
299 	.detach		= comedi_legacy_detach,
300 };
301 module_comedi_driver(parport_driver);
302 
303 MODULE_AUTHOR("Comedi https://www.comedi.org");
304 MODULE_DESCRIPTION("Comedi: Standard parallel port driver");
305 MODULE_LICENSE("GPL");
306