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