comedi_parport.c revision 241ab6ad7108e51c67881f7881c5e46f0400cdb1
1/*
2    comedi/drivers/comedi_parport.c
3    hardware driver for standard parallel port
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 1998,2001 David A. Schleef <ds@schleef.org>
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 2 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22*/
23/*
24Driver: comedi_parport
25Description: Standard PC parallel port
26Author: ds
27Status: works in immediate mode
28Devices: [standard] parallel port (comedi_parport)
29Updated: Tue, 30 Apr 2002 21:11:45 -0700
30
31A cheap and easy way to get a few more digital I/O lines.  Steal
32additional parallel ports from old computers or your neighbors'
33computers.
34
35Option list:
36 0: I/O port base for the parallel port.
37 1: IRQ
38
39Parallel Port Lines:
40
41pin     subdev  chan    aka
42---     ------  ----    ---
431       2       0       strobe
442       0       0       data 0
453       0       1       data 1
464       0       2       data 2
475       0       3       data 3
486       0       4       data 4
497       0       5       data 5
508       0       6       data 6
519       0       7       data 7
5210      1       3       acknowledge
5311      1       4       busy
5412      1       2       output
5513      1       1       printer selected
5614      2       1       auto LF
5715      1       0       error
5816      2       2       init
5917      2       3       select printer
6018-25   ground
61
62Notes:
63
64Subdevices 0 is digital I/O, subdevice 1 is digital input, and
65subdevice 2 is digital output.  Unlike other Comedi devices,
66subdevice 0 defaults to output.
67
68Pins 13 and 14 are inverted once by Comedi and once by the
69hardware, thus cancelling the effect.
70
71Pin 1 is a strobe, thus acts like one.  There's no way in software
72to change this, at least on a standard parallel port.
73
74Subdevice 3 pretends to be a digital input subdevice, but it always
75returns 0 when read.  However, if you run a command with
76scan_begin_src=TRIG_EXT, it uses pin 10 as a external triggering
77pin, which can be used to wake up tasks.
78*/
79/*
80   see http://www.beyondlogic.org/ for information.
81   or http://www.linux-magazin.de/ausgabe/1999/10/IO/io.html
82 */
83
84#include "../comedidev.h"
85#include <linux/ioport.h>
86
87#define PARPORT_SIZE 3
88
89#define PARPORT_A 0
90#define PARPORT_B 1
91#define PARPORT_C 2
92
93static int parport_attach(comedi_device * dev, comedi_devconfig * it);
94static int parport_detach(comedi_device * dev);
95static comedi_driver driver_parport = {
96      driver_name:"comedi_parport",
97      module:THIS_MODULE,
98      attach:parport_attach,
99      detach:parport_detach,
100};
101
102COMEDI_INITCLEANUP(driver_parport);
103
104typedef struct parport_private_struct {
105	unsigned int a_data;
106	unsigned int c_data;
107	int enable_irq;
108} parport_private;
109#define devpriv ((parport_private *)(dev->private))
110
111static int parport_insn_a(comedi_device * dev, comedi_subdevice * s,
112	comedi_insn * insn, lsampl_t * data)
113{
114	if (data[0]) {
115		devpriv->a_data &= ~data[0];
116		devpriv->a_data |= (data[0] & data[1]);
117
118		outb(devpriv->a_data, dev->iobase + PARPORT_A);
119	}
120
121	data[1] = inb(dev->iobase + PARPORT_A);
122
123	return 2;
124}
125
126static int parport_insn_config_a(comedi_device * dev, comedi_subdevice * s,
127	comedi_insn * insn, lsampl_t * data)
128{
129	if (data[0]) {
130		s->io_bits = 0xff;
131		devpriv->c_data &= ~(1 << 5);
132	} else {
133		s->io_bits = 0;
134		devpriv->c_data |= (1 << 5);
135	}
136	outb(devpriv->c_data, dev->iobase + PARPORT_C);
137
138	return 1;
139}
140
141static int parport_insn_b(comedi_device * dev, comedi_subdevice * s,
142	comedi_insn * insn, lsampl_t * data)
143{
144	if (data[0]) {
145		// should writes be ignored?
146	}
147
148	data[1] = (inb(dev->iobase + PARPORT_B) >> 3);
149
150	return 2;
151}
152
153static int parport_insn_c(comedi_device * dev, comedi_subdevice * s,
154	comedi_insn * insn, lsampl_t * data)
155{
156	data[0] &= 0x0f;
157	if (data[0]) {
158		devpriv->c_data &= ~data[0];
159		devpriv->c_data |= (data[0] & data[1]);
160
161		outb(devpriv->c_data, dev->iobase + PARPORT_C);
162	}
163
164	data[1] = devpriv->c_data & 0xf;
165
166	return 2;
167}
168
169static int parport_intr_insn(comedi_device * dev, comedi_subdevice * s,
170	comedi_insn * insn, lsampl_t * data)
171{
172	if (insn->n < 1)
173		return -EINVAL;
174
175	data[1] = 0;
176	return 2;
177}
178
179static int parport_intr_cmdtest(comedi_device * dev, comedi_subdevice * s,
180	comedi_cmd * cmd)
181{
182	int err = 0;
183	int tmp;
184
185	/* step 1 */
186
187	tmp = cmd->start_src;
188	cmd->start_src &= TRIG_NOW;
189	if (!cmd->start_src || tmp != cmd->start_src)
190		err++;
191
192	tmp = cmd->scan_begin_src;
193	cmd->scan_begin_src &= TRIG_EXT;
194	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
195		err++;
196
197	tmp = cmd->convert_src;
198	cmd->convert_src &= TRIG_FOLLOW;
199	if (!cmd->convert_src || tmp != cmd->convert_src)
200		err++;
201
202	tmp = cmd->scan_end_src;
203	cmd->scan_end_src &= TRIG_COUNT;
204	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
205		err++;
206
207	tmp = cmd->stop_src;
208	cmd->stop_src &= TRIG_NONE;
209	if (!cmd->stop_src || tmp != cmd->stop_src)
210		err++;
211
212	if (err)
213		return 1;
214
215	/* step 2: ignored */
216
217	if (err)
218		return 2;
219
220	/* step 3: */
221
222	if (cmd->start_arg != 0) {
223		cmd->start_arg = 0;
224		err++;
225	}
226	if (cmd->scan_begin_arg != 0) {
227		cmd->scan_begin_arg = 0;
228		err++;
229	}
230	if (cmd->convert_arg != 0) {
231		cmd->convert_arg = 0;
232		err++;
233	}
234	if (cmd->scan_end_arg != 1) {
235		cmd->scan_end_arg = 1;
236		err++;
237	}
238	if (cmd->stop_arg != 0) {
239		cmd->stop_arg = 0;
240		err++;
241	}
242
243	if (err)
244		return 3;
245
246	/* step 4: ignored */
247
248	if (err)
249		return 4;
250
251	return 0;
252}
253
254static int parport_intr_cmd(comedi_device * dev, comedi_subdevice * s)
255{
256	devpriv->c_data |= 0x10;
257	outb(devpriv->c_data, dev->iobase + PARPORT_C);
258
259	devpriv->enable_irq = 1;
260
261	return 0;
262}
263
264static int parport_intr_cancel(comedi_device * dev, comedi_subdevice * s)
265{
266	printk("parport_intr_cancel()\n");
267
268	devpriv->c_data &= ~0x10;
269	outb(devpriv->c_data, dev->iobase + PARPORT_C);
270
271	devpriv->enable_irq = 0;
272
273	return 0;
274}
275
276static irqreturn_t parport_interrupt(int irq, void *d PT_REGS_ARG)
277{
278	comedi_device *dev = d;
279	comedi_subdevice *s = dev->subdevices + 3;
280
281	if (!devpriv->enable_irq) {
282		printk("comedi_parport: bogus irq, ignored\n");
283		return IRQ_NONE;
284	}
285
286	comedi_buf_put(s->async, 0);
287	s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
288
289	comedi_event(dev, s);
290	return IRQ_HANDLED;
291}
292
293static int parport_attach(comedi_device * dev, comedi_devconfig * it)
294{
295	int ret;
296	unsigned int irq;
297	unsigned long iobase;
298	comedi_subdevice *s;
299
300	iobase = it->options[0];
301	printk("comedi%d: parport: 0x%04lx ", dev->minor, iobase);
302	if (!request_region(iobase, PARPORT_SIZE, "parport (comedi)")) {
303		printk("I/O port conflict\n");
304		return -EIO;
305	}
306	dev->iobase = iobase;
307
308	irq = it->options[1];
309	if (irq) {
310		printk(" irq=%u", irq);
311		ret = comedi_request_irq(irq, parport_interrupt, 0,
312			"comedi_parport", dev);
313		if (ret < 0) {
314			printk(" irq not available\n");
315			return -EINVAL;
316		}
317		dev->irq = irq;
318	}
319	dev->board_name = "parport";
320
321	if ((ret = alloc_subdevices(dev, 4)) < 0)
322		return ret;
323	if ((ret = alloc_private(dev, sizeof(parport_private))) < 0)
324		return ret;
325
326	s = dev->subdevices + 0;
327	s->type = COMEDI_SUBD_DIO;
328	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
329	s->n_chan = 8;
330	s->maxdata = 1;
331	s->range_table = &range_digital;
332	s->insn_bits = parport_insn_a;
333	s->insn_config = parport_insn_config_a;
334
335	s = dev->subdevices + 1;
336	s->type = COMEDI_SUBD_DI;
337	s->subdev_flags = SDF_READABLE;
338	s->n_chan = 5;
339	s->maxdata = 1;
340	s->range_table = &range_digital;
341	s->insn_bits = parport_insn_b;
342
343	s = dev->subdevices + 2;
344	s->type = COMEDI_SUBD_DO;
345	s->subdev_flags = SDF_WRITABLE;
346	s->n_chan = 4;
347	s->maxdata = 1;
348	s->range_table = &range_digital;
349	s->insn_bits = parport_insn_c;
350
351	s = dev->subdevices + 3;
352	if (irq) {
353		dev->read_subdev = s;
354		s->type = COMEDI_SUBD_DI;
355		s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
356		s->n_chan = 1;
357		s->maxdata = 1;
358		s->range_table = &range_digital;
359		s->insn_bits = parport_intr_insn;
360		s->do_cmdtest = parport_intr_cmdtest;
361		s->do_cmd = parport_intr_cmd;
362		s->cancel = parport_intr_cancel;
363	} else {
364		s->type = COMEDI_SUBD_UNUSED;
365	}
366
367	devpriv->a_data = 0;
368	outb(devpriv->a_data, dev->iobase + PARPORT_A);
369	devpriv->c_data = 0;
370	outb(devpriv->c_data, dev->iobase + PARPORT_C);
371
372	printk("\n");
373	return 1;
374}
375
376static int parport_detach(comedi_device * dev)
377{
378	printk("comedi%d: parport: remove\n", dev->minor);
379
380	if (dev->iobase)
381		release_region(dev->iobase, PARPORT_SIZE);
382
383	if (dev->irq)
384		comedi_free_irq(dev->irq, dev);
385
386	return 0;
387}
388