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