8255.c revision 0a85b6f0ab0d2edb0d41b32697111ce0e4f43496
1/*
2    comedi/drivers/8255.c
3    Driver for 8255
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 1998 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: 8255
25Description: generic 8255 support
26Devices: [standard] 8255 (8255)
27Author: ds
28Status: works
29Updated: Fri,  7 Jun 2002 12:56:45 -0700
30
31The classic in digital I/O.  The 8255 appears in Comedi as a single
32digital I/O subdevice with 24 channels.  The channel 0 corresponds
33to the 8255's port A, bit 0; channel 23 corresponds to port C, bit
347.  Direction configuration is done in blocks, with channels 0-7,
358-15, 16-19, and 20-23 making up the 4 blocks.  The only 8255 mode
36supported is mode 0.
37
38You should enable compilation this driver if you plan to use a board
39that has an 8255 chip.  For multifunction boards, the main driver will
40configure the 8255 subdevice automatically.
41
42This driver also works independently with ISA and PCI cards that
43directly map the 8255 registers to I/O ports, including cards with
44multiple 8255 chips.  To configure the driver for such a card, the
45option list should be a list of the I/O port bases for each of the
468255 chips.  For example,
47
48  comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c
49
50Note that most PCI 8255 boards do NOT work with this driver, and
51need a separate driver as a wrapper.  For those that do work, the
52I/O port base address can be found in the output of 'lspci -v'.
53
54*/
55
56/*
57   This file contains an exported subdevice for driving an 8255.
58
59   To use this subdevice as part of another driver, you need to
60   set up the subdevice in the attach function of the driver by
61   calling:
62
63     subdev_8255_init(device, subdevice, callback_function, arg)
64
65   device and subdevice are pointers to the device and subdevice
66   structures.  callback_function will be called to provide the
67   low-level input/output to the device, i.e., actual register
68   access.  callback_function will be called with the value of arg
69   as the last parameter.  If the 8255 device is mapped as 4
70   consecutive I/O ports, you can use NULL for callback_function
71   and the I/O port base for arg, and an internal function will
72   handle the register access.
73
74   In addition, if the main driver handles interrupts, you can
75   enable commands on the subdevice by calling subdev_8255_init_irq()
76   instead.  Then, when you get an interrupt that is likely to be
77   from the 8255, you should call subdev_8255_interrupt(), which
78   will copy the latched value to a Comedi buffer.
79 */
80
81#include "../comedidev.h"
82
83#include <linux/ioport.h>
84
85#define _8255_SIZE 4
86
87#define _8255_DATA 0
88#define _8255_CR 3
89
90#define CR_C_LO_IO	0x01
91#define CR_B_IO		0x02
92#define CR_B_MODE	0x04
93#define CR_C_HI_IO	0x08
94#define CR_A_IO		0x10
95#define CR_A_MODE(a)	((a)<<5)
96#define CR_CW		0x80
97
98struct subdev_8255_struct {
99	unsigned long cb_arg;
100	int (*cb_func) (int, int, int, unsigned long);
101	int have_irq;
102};
103
104#define CALLBACK_ARG	(((struct subdev_8255_struct *)s->private)->cb_arg)
105#define CALLBACK_FUNC	(((struct subdev_8255_struct *)s->private)->cb_func)
106#define subdevpriv	((struct subdev_8255_struct *)s->private)
107
108static int dev_8255_attach(struct comedi_device *dev,
109			   struct comedi_devconfig *it);
110static int dev_8255_detach(struct comedi_device *dev);
111static struct comedi_driver driver_8255 = {
112	.driver_name = "8255",
113	.module = THIS_MODULE,
114	.attach = dev_8255_attach,
115	.detach = dev_8255_detach,
116};
117
118COMEDI_INITCLEANUP(driver_8255);
119
120static void do_config(struct comedi_device *dev, struct comedi_subdevice *s);
121
122void subdev_8255_interrupt(struct comedi_device *dev,
123			   struct comedi_subdevice *s)
124{
125	short d;
126
127	d = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
128	d |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
129
130	comedi_buf_put(s->async, d);
131	s->async->events |= COMEDI_CB_EOS;
132
133	comedi_event(dev, s);
134}
135
136static int subdev_8255_cb(int dir, int port, int data, unsigned long arg)
137{
138	unsigned long iobase = arg;
139
140	if (dir) {
141		outb(data, iobase + port);
142		return 0;
143	} else {
144		return inb(iobase + port);
145	}
146}
147
148static int subdev_8255_insn(struct comedi_device *dev,
149			    struct comedi_subdevice *s,
150			    struct comedi_insn *insn, unsigned int *data)
151{
152	if (data[0]) {
153		s->state &= ~data[0];
154		s->state |= (data[0] & data[1]);
155
156		if (data[0] & 0xff)
157			CALLBACK_FUNC(1, _8255_DATA, s->state & 0xff,
158				      CALLBACK_ARG);
159		if (data[0] & 0xff00)
160			CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff,
161				      CALLBACK_ARG);
162		if (data[0] & 0xff0000)
163			CALLBACK_FUNC(1, _8255_DATA + 2,
164				      (s->state >> 16) & 0xff, CALLBACK_ARG);
165	}
166
167	data[1] = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
168	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
169	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 2, 0, CALLBACK_ARG) << 16);
170
171	return 2;
172}
173
174static int subdev_8255_insn_config(struct comedi_device *dev,
175				   struct comedi_subdevice *s,
176				   struct comedi_insn *insn, unsigned int *data)
177{
178	unsigned int mask;
179	unsigned int bits;
180
181	mask = 1 << CR_CHAN(insn->chanspec);
182	if (mask & 0x0000ff) {
183		bits = 0x0000ff;
184	} else if (mask & 0x00ff00) {
185		bits = 0x00ff00;
186	} else if (mask & 0x0f0000) {
187		bits = 0x0f0000;
188	} else {
189		bits = 0xf00000;
190	}
191
192	switch (data[0]) {
193	case INSN_CONFIG_DIO_INPUT:
194		s->io_bits &= ~bits;
195		break;
196	case INSN_CONFIG_DIO_OUTPUT:
197		s->io_bits |= bits;
198		break;
199	case INSN_CONFIG_DIO_QUERY:
200		data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
201		return insn->n;
202		break;
203	default:
204		return -EINVAL;
205	}
206
207	do_config(dev, s);
208
209	return 1;
210}
211
212static void do_config(struct comedi_device *dev, struct comedi_subdevice *s)
213{
214	int config;
215
216	config = CR_CW;
217	/* 1 in io_bits indicates output, 1 in config indicates input */
218	if (!(s->io_bits & 0x0000ff))
219		config |= CR_A_IO;
220	if (!(s->io_bits & 0x00ff00))
221		config |= CR_B_IO;
222	if (!(s->io_bits & 0x0f0000))
223		config |= CR_C_LO_IO;
224	if (!(s->io_bits & 0xf00000))
225		config |= CR_C_HI_IO;
226	CALLBACK_FUNC(1, _8255_CR, config, CALLBACK_ARG);
227}
228
229static int subdev_8255_cmdtest(struct comedi_device *dev,
230			       struct comedi_subdevice *s,
231			       struct comedi_cmd *cmd)
232{
233	int err = 0;
234	unsigned int tmp;
235
236	/* step 1 */
237
238	tmp = cmd->start_src;
239	cmd->start_src &= TRIG_NOW;
240	if (!cmd->start_src || tmp != cmd->start_src)
241		err++;
242
243	tmp = cmd->scan_begin_src;
244	cmd->scan_begin_src &= TRIG_EXT;
245	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
246		err++;
247
248	tmp = cmd->convert_src;
249	cmd->convert_src &= TRIG_FOLLOW;
250	if (!cmd->convert_src || tmp != cmd->convert_src)
251		err++;
252
253	tmp = cmd->scan_end_src;
254	cmd->scan_end_src &= TRIG_COUNT;
255	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
256		err++;
257
258	tmp = cmd->stop_src;
259	cmd->stop_src &= TRIG_NONE;
260	if (!cmd->stop_src || tmp != cmd->stop_src)
261		err++;
262
263	if (err)
264		return 1;
265
266	/* step 2 */
267
268	if (err)
269		return 2;
270
271	/* step 3 */
272
273	if (cmd->start_arg != 0) {
274		cmd->start_arg = 0;
275		err++;
276	}
277	if (cmd->scan_begin_arg != 0) {
278		cmd->scan_begin_arg = 0;
279		err++;
280	}
281	if (cmd->convert_arg != 0) {
282		cmd->convert_arg = 0;
283		err++;
284	}
285	if (cmd->scan_end_arg != 1) {
286		cmd->scan_end_arg = 1;
287		err++;
288	}
289	if (cmd->stop_arg != 0) {
290		cmd->stop_arg = 0;
291		err++;
292	}
293
294	if (err)
295		return 3;
296
297	/* step 4 */
298
299	if (err)
300		return 4;
301
302	return 0;
303}
304
305static int subdev_8255_cmd(struct comedi_device *dev,
306			   struct comedi_subdevice *s)
307{
308	/* FIXME */
309
310	return 0;
311}
312
313static int subdev_8255_cancel(struct comedi_device *dev,
314			      struct comedi_subdevice *s)
315{
316	/* FIXME */
317
318	return 0;
319}
320
321int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s,
322		     int (*cb) (int, int, int, unsigned long),
323		     unsigned long arg)
324{
325	s->type = COMEDI_SUBD_DIO;
326	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
327	s->n_chan = 24;
328	s->range_table = &range_digital;
329	s->maxdata = 1;
330
331	s->private = kmalloc(sizeof(struct subdev_8255_struct), GFP_KERNEL);
332	if (!s->private)
333		return -ENOMEM;
334
335	CALLBACK_ARG = arg;
336	if (cb == NULL) {
337		CALLBACK_FUNC = subdev_8255_cb;
338	} else {
339		CALLBACK_FUNC = cb;
340	}
341	s->insn_bits = subdev_8255_insn;
342	s->insn_config = subdev_8255_insn_config;
343
344	s->state = 0;
345	s->io_bits = 0;
346	do_config(dev, s);
347
348	return 0;
349}
350
351int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s,
352			 int (*cb) (int, int, int, unsigned long),
353			 unsigned long arg)
354{
355	int ret;
356
357	ret = subdev_8255_init(dev, s, cb, arg);
358	if (ret < 0)
359		return ret;
360
361	s->do_cmdtest = subdev_8255_cmdtest;
362	s->do_cmd = subdev_8255_cmd;
363	s->cancel = subdev_8255_cancel;
364
365	subdevpriv->have_irq = 1;
366
367	return 0;
368}
369
370void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s)
371{
372	if (s->private) {
373		/* this test does nothing, so comment it out
374		 * if (subdevpriv->have_irq) {
375		 * }
376		 */
377
378		kfree(s->private);
379	}
380}
381
382/*
383
384   Start of the 8255 standalone device
385
386 */
387
388static int dev_8255_attach(struct comedi_device *dev,
389			   struct comedi_devconfig *it)
390{
391	int ret;
392	unsigned long iobase;
393	int i;
394
395	printk("comedi%d: 8255:", dev->minor);
396
397	dev->board_name = "8255";
398
399	for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
400		iobase = it->options[i];
401		if (!iobase)
402			break;
403	}
404	if (i == 0) {
405		printk(" no devices specified\n");
406		return -EINVAL;
407	}
408
409	ret = alloc_subdevices(dev, i);
410	if (ret < 0)
411		return ret;
412
413	for (i = 0; i < dev->n_subdevices; i++) {
414		iobase = it->options[i];
415
416		printk(" 0x%04lx", iobase);
417		if (!request_region(iobase, _8255_SIZE, "8255")) {
418			printk(" (I/O port conflict)");
419
420			dev->subdevices[i].type = COMEDI_SUBD_UNUSED;
421		} else {
422			subdev_8255_init(dev, dev->subdevices + i, NULL,
423					 iobase);
424		}
425	}
426
427	printk("\n");
428
429	return 0;
430}
431
432static int dev_8255_detach(struct comedi_device *dev)
433{
434	int i;
435	unsigned long iobase;
436	struct comedi_subdevice *s;
437
438	printk("comedi%d: 8255: remove\n", dev->minor);
439
440	for (i = 0; i < dev->n_subdevices; i++) {
441		s = dev->subdevices + i;
442		if (s->type != COMEDI_SUBD_UNUSED) {
443			iobase = CALLBACK_ARG;
444			release_region(iobase, _8255_SIZE);
445		}
446		subdev_8255_cleanup(dev, s);
447	}
448
449	return 0;
450}
451
452EXPORT_SYMBOL(subdev_8255_init);
453EXPORT_SYMBOL(subdev_8255_init_irq);
454EXPORT_SYMBOL(subdev_8255_cleanup);
455EXPORT_SYMBOL(subdev_8255_interrupt);
456