8255.c revision c5efe58b83bc5c1d5811800faf03b1089d1ef054
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#include <linux/slab.h>
85#include "8255.h"
86
87#define _8255_SIZE 4
88
89#define _8255_DATA 0
90#define _8255_CR 3
91
92#define CR_C_LO_IO	0x01
93#define CR_B_IO		0x02
94#define CR_B_MODE	0x04
95#define CR_C_HI_IO	0x08
96#define CR_A_IO		0x10
97#define CR_A_MODE(a)	((a)<<5)
98#define CR_CW		0x80
99
100struct subdev_8255_struct {
101	unsigned long cb_arg;
102	int (*cb_func) (int, int, int, unsigned long);
103	int have_irq;
104};
105
106#define CALLBACK_ARG	(((struct subdev_8255_struct *)s->private)->cb_arg)
107#define CALLBACK_FUNC	(((struct subdev_8255_struct *)s->private)->cb_func)
108#define subdevpriv	((struct subdev_8255_struct *)s->private)
109
110static int dev_8255_attach(struct comedi_device *dev,
111			   struct comedi_devconfig *it);
112static int dev_8255_detach(struct comedi_device *dev);
113static struct comedi_driver driver_8255 = {
114	.driver_name = "8255",
115	.module = THIS_MODULE,
116	.attach = dev_8255_attach,
117	.detach = dev_8255_detach,
118};
119
120COMEDI_INITCLEANUP(driver_8255);
121
122static void do_config(struct comedi_device *dev, struct comedi_subdevice *s);
123
124void subdev_8255_interrupt(struct comedi_device *dev,
125			   struct comedi_subdevice *s)
126{
127	short d;
128
129	d = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
130	d |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
131
132	comedi_buf_put(s->async, d);
133	s->async->events |= COMEDI_CB_EOS;
134
135	comedi_event(dev, s);
136}
137EXPORT_SYMBOL(subdev_8255_interrupt);
138
139static int subdev_8255_cb(int dir, int port, int data, unsigned long arg)
140{
141	unsigned long iobase = arg;
142
143	if (dir) {
144		outb(data, iobase + port);
145		return 0;
146	} else {
147		return inb(iobase + port);
148	}
149}
150
151static int subdev_8255_insn(struct comedi_device *dev,
152			    struct comedi_subdevice *s,
153			    struct comedi_insn *insn, unsigned int *data)
154{
155	if (data[0]) {
156		s->state &= ~data[0];
157		s->state |= (data[0] & data[1]);
158
159		if (data[0] & 0xff)
160			CALLBACK_FUNC(1, _8255_DATA, s->state & 0xff,
161				      CALLBACK_ARG);
162		if (data[0] & 0xff00)
163			CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff,
164				      CALLBACK_ARG);
165		if (data[0] & 0xff0000)
166			CALLBACK_FUNC(1, _8255_DATA + 2,
167				      (s->state >> 16) & 0xff, CALLBACK_ARG);
168	}
169
170	data[1] = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
171	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
172	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 2, 0, CALLBACK_ARG) << 16);
173
174	return 2;
175}
176
177static int subdev_8255_insn_config(struct comedi_device *dev,
178				   struct comedi_subdevice *s,
179				   struct comedi_insn *insn, unsigned int *data)
180{
181	unsigned int mask;
182	unsigned int bits;
183
184	mask = 1 << CR_CHAN(insn->chanspec);
185	if (mask & 0x0000ff)
186		bits = 0x0000ff;
187	else if (mask & 0x00ff00)
188		bits = 0x00ff00;
189	else if (mask & 0x0f0000)
190		bits = 0x0f0000;
191	else
192		bits = 0xf00000;
193
194	switch (data[0]) {
195	case INSN_CONFIG_DIO_INPUT:
196		s->io_bits &= ~bits;
197		break;
198	case INSN_CONFIG_DIO_OUTPUT:
199		s->io_bits |= bits;
200		break;
201	case INSN_CONFIG_DIO_QUERY:
202		data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
203		return insn->n;
204		break;
205	default:
206		return -EINVAL;
207	}
208
209	do_config(dev, s);
210
211	return 1;
212}
213
214static void do_config(struct comedi_device *dev, struct comedi_subdevice *s)
215{
216	int config;
217
218	config = CR_CW;
219	/* 1 in io_bits indicates output, 1 in config indicates input */
220	if (!(s->io_bits & 0x0000ff))
221		config |= CR_A_IO;
222	if (!(s->io_bits & 0x00ff00))
223		config |= CR_B_IO;
224	if (!(s->io_bits & 0x0f0000))
225		config |= CR_C_LO_IO;
226	if (!(s->io_bits & 0xf00000))
227		config |= CR_C_HI_IO;
228	CALLBACK_FUNC(1, _8255_CR, config, CALLBACK_ARG);
229}
230
231static int subdev_8255_cmdtest(struct comedi_device *dev,
232			       struct comedi_subdevice *s,
233			       struct comedi_cmd *cmd)
234{
235	int err = 0;
236	unsigned int tmp;
237
238	/* step 1 */
239
240	tmp = cmd->start_src;
241	cmd->start_src &= TRIG_NOW;
242	if (!cmd->start_src || tmp != cmd->start_src)
243		err++;
244
245	tmp = cmd->scan_begin_src;
246	cmd->scan_begin_src &= TRIG_EXT;
247	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
248		err++;
249
250	tmp = cmd->convert_src;
251	cmd->convert_src &= TRIG_FOLLOW;
252	if (!cmd->convert_src || tmp != cmd->convert_src)
253		err++;
254
255	tmp = cmd->scan_end_src;
256	cmd->scan_end_src &= TRIG_COUNT;
257	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
258		err++;
259
260	tmp = cmd->stop_src;
261	cmd->stop_src &= TRIG_NONE;
262	if (!cmd->stop_src || tmp != cmd->stop_src)
263		err++;
264
265	if (err)
266		return 1;
267
268	/* step 2 */
269
270	if (err)
271		return 2;
272
273	/* step 3 */
274
275	if (cmd->start_arg != 0) {
276		cmd->start_arg = 0;
277		err++;
278	}
279	if (cmd->scan_begin_arg != 0) {
280		cmd->scan_begin_arg = 0;
281		err++;
282	}
283	if (cmd->convert_arg != 0) {
284		cmd->convert_arg = 0;
285		err++;
286	}
287	if (cmd->scan_end_arg != 1) {
288		cmd->scan_end_arg = 1;
289		err++;
290	}
291	if (cmd->stop_arg != 0) {
292		cmd->stop_arg = 0;
293		err++;
294	}
295
296	if (err)
297		return 3;
298
299	/* step 4 */
300
301	if (err)
302		return 4;
303
304	return 0;
305}
306
307static int subdev_8255_cmd(struct comedi_device *dev,
308			   struct comedi_subdevice *s)
309{
310	/* FIXME */
311
312	return 0;
313}
314
315static int subdev_8255_cancel(struct comedi_device *dev,
316			      struct comedi_subdevice *s)
317{
318	/* FIXME */
319
320	return 0;
321}
322
323int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s,
324		     int (*cb) (int, int, int, unsigned long),
325		     unsigned long arg)
326{
327	s->type = COMEDI_SUBD_DIO;
328	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
329	s->n_chan = 24;
330	s->range_table = &range_digital;
331	s->maxdata = 1;
332
333	s->private = kmalloc(sizeof(struct subdev_8255_struct), GFP_KERNEL);
334	if (!s->private)
335		return -ENOMEM;
336
337	CALLBACK_ARG = arg;
338	if (cb == NULL)
339		CALLBACK_FUNC = subdev_8255_cb;
340	else
341		CALLBACK_FUNC = cb;
342	s->insn_bits = subdev_8255_insn;
343	s->insn_config = subdev_8255_insn_config;
344
345	s->state = 0;
346	s->io_bits = 0;
347	do_config(dev, s);
348
349	return 0;
350}
351EXPORT_SYMBOL(subdev_8255_init);
352
353int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s,
354			 int (*cb) (int, int, int, unsigned long),
355			 unsigned long arg)
356{
357	int ret;
358
359	ret = subdev_8255_init(dev, s, cb, arg);
360	if (ret < 0)
361		return ret;
362
363	s->do_cmdtest = subdev_8255_cmdtest;
364	s->do_cmd = subdev_8255_cmd;
365	s->cancel = subdev_8255_cancel;
366
367	subdevpriv->have_irq = 1;
368
369	return 0;
370}
371EXPORT_SYMBOL(subdev_8255_init_irq);
372
373void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s)
374{
375	if (s->private) {
376		/* this test does nothing, so comment it out
377		 * if (subdevpriv->have_irq) {
378		 * }
379		 */
380
381		kfree(s->private);
382	}
383}
384EXPORT_SYMBOL(subdev_8255_cleanup);
385
386/*
387
388   Start of the 8255 standalone device
389
390 */
391
392static int dev_8255_attach(struct comedi_device *dev,
393			   struct comedi_devconfig *it)
394{
395	int ret;
396	unsigned long iobase;
397	int i;
398
399	printk("comedi%d: 8255:", dev->minor);
400
401	dev->board_name = "8255";
402
403	for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
404		iobase = it->options[i];
405		if (!iobase)
406			break;
407	}
408	if (i == 0) {
409		printk(" no devices specified\n");
410		return -EINVAL;
411	}
412
413	ret = alloc_subdevices(dev, i);
414	if (ret < 0)
415		return ret;
416
417	for (i = 0; i < dev->n_subdevices; i++) {
418		iobase = it->options[i];
419
420		printk(" 0x%04lx", iobase);
421		if (!request_region(iobase, _8255_SIZE, "8255")) {
422			printk(" (I/O port conflict)");
423
424			dev->subdevices[i].type = COMEDI_SUBD_UNUSED;
425		} else {
426			subdev_8255_init(dev, dev->subdevices + i, NULL,
427					 iobase);
428		}
429	}
430
431	printk("\n");
432
433	return 0;
434}
435
436static int dev_8255_detach(struct comedi_device *dev)
437{
438	int i;
439	unsigned long iobase;
440	struct comedi_subdevice *s;
441
442	printk("comedi%d: 8255: remove\n", dev->minor);
443
444	for (i = 0; i < dev->n_subdevices; i++) {
445		s = dev->subdevices + i;
446		if (s->type != COMEDI_SUBD_UNUSED) {
447			iobase = CALLBACK_ARG;
448			release_region(iobase, _8255_SIZE);
449		}
450		subdev_8255_cleanup(dev, s);
451	}
452
453	return 0;
454}
455