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 /*
24 Driver: 8255
25 Description: generic 8255 support
26 Devices: [standard] 8255 (8255)
27 Author: ds
28 Status: works
29 Updated: Fri,  7 Jun 2002 12:56:45 -0700
30 
31 The classic in digital I/O.  The 8255 appears in Comedi as a single
32 digital I/O subdevice with 24 channels.  The channel 0 corresponds
33 to the 8255's port A, bit 0; channel 23 corresponds to port C, bit
34 7.  Direction configuration is done in blocks, with channels 0-7,
35 8-15, 16-19, and 20-23 making up the 4 blocks.  The only 8255 mode
36 supported is mode 0.
37 
38 You should enable compilation this driver if you plan to use a board
39 that has an 8255 chip.  For multifunction boards, the main driver will
40 configure the 8255 subdevice automatically.
41 
42 This driver also works independently with ISA and PCI cards that
43 directly map the 8255 registers to I/O ports, including cards with
44 multiple 8255 chips.  To configure the driver for such a card, the
45 option list should be a list of the I/O port bases for each of the
46 8255 chips.  For example,
47 
48   comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c
49 
50 Note that most PCI 8255 boards do NOT work with this driver, and
51 need a separate driver as a wrapper.  For those that do work, the
52 I/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 
100 struct 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 
110 static int dev_8255_attach(struct comedi_device *dev,
111 			   struct comedi_devconfig *it);
112 static int dev_8255_detach(struct comedi_device *dev);
113 static 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 
driver_8255_init_module(void)120 static int __init driver_8255_init_module(void)
121 {
122 	return comedi_driver_register(&driver_8255);
123 }
124 
driver_8255_cleanup_module(void)125 static void __exit driver_8255_cleanup_module(void)
126 {
127 	comedi_driver_unregister(&driver_8255);
128 }
129 
130 module_init(driver_8255_init_module);
131 module_exit(driver_8255_cleanup_module);
132 
133 static void do_config(struct comedi_device *dev, struct comedi_subdevice *s);
134 
subdev_8255_interrupt(struct comedi_device * dev,struct comedi_subdevice * s)135 void subdev_8255_interrupt(struct comedi_device *dev,
136 			   struct comedi_subdevice *s)
137 {
138 	short d;
139 
140 	d = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
141 	d |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
142 
143 	comedi_buf_put(s->async, d);
144 	s->async->events |= COMEDI_CB_EOS;
145 
146 	comedi_event(dev, s);
147 }
148 EXPORT_SYMBOL(subdev_8255_interrupt);
149 
subdev_8255_cb(int dir,int port,int data,unsigned long arg)150 static int subdev_8255_cb(int dir, int port, int data, unsigned long arg)
151 {
152 	unsigned long iobase = arg;
153 
154 	if (dir) {
155 		outb(data, iobase + port);
156 		return 0;
157 	} else {
158 		return inb(iobase + port);
159 	}
160 }
161 
subdev_8255_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)162 static int subdev_8255_insn(struct comedi_device *dev,
163 			    struct comedi_subdevice *s,
164 			    struct comedi_insn *insn, unsigned int *data)
165 {
166 	if (data[0]) {
167 		s->state &= ~data[0];
168 		s->state |= (data[0] & data[1]);
169 
170 		if (data[0] & 0xff)
171 			CALLBACK_FUNC(1, _8255_DATA, s->state & 0xff,
172 				      CALLBACK_ARG);
173 		if (data[0] & 0xff00)
174 			CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff,
175 				      CALLBACK_ARG);
176 		if (data[0] & 0xff0000)
177 			CALLBACK_FUNC(1, _8255_DATA + 2,
178 				      (s->state >> 16) & 0xff, CALLBACK_ARG);
179 	}
180 
181 	data[1] = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
182 	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
183 	data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 2, 0, CALLBACK_ARG) << 16);
184 
185 	return 2;
186 }
187 
subdev_8255_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)188 static int subdev_8255_insn_config(struct comedi_device *dev,
189 				   struct comedi_subdevice *s,
190 				   struct comedi_insn *insn, unsigned int *data)
191 {
192 	unsigned int mask;
193 	unsigned int bits;
194 
195 	mask = 1 << CR_CHAN(insn->chanspec);
196 	if (mask & 0x0000ff)
197 		bits = 0x0000ff;
198 	else if (mask & 0x00ff00)
199 		bits = 0x00ff00;
200 	else if (mask & 0x0f0000)
201 		bits = 0x0f0000;
202 	else
203 		bits = 0xf00000;
204 
205 	switch (data[0]) {
206 	case INSN_CONFIG_DIO_INPUT:
207 		s->io_bits &= ~bits;
208 		break;
209 	case INSN_CONFIG_DIO_OUTPUT:
210 		s->io_bits |= bits;
211 		break;
212 	case INSN_CONFIG_DIO_QUERY:
213 		data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
214 		return insn->n;
215 		break;
216 	default:
217 		return -EINVAL;
218 	}
219 
220 	do_config(dev, s);
221 
222 	return 1;
223 }
224 
do_config(struct comedi_device * dev,struct comedi_subdevice * s)225 static void do_config(struct comedi_device *dev, struct comedi_subdevice *s)
226 {
227 	int config;
228 
229 	config = CR_CW;
230 	/* 1 in io_bits indicates output, 1 in config indicates input */
231 	if (!(s->io_bits & 0x0000ff))
232 		config |= CR_A_IO;
233 	if (!(s->io_bits & 0x00ff00))
234 		config |= CR_B_IO;
235 	if (!(s->io_bits & 0x0f0000))
236 		config |= CR_C_LO_IO;
237 	if (!(s->io_bits & 0xf00000))
238 		config |= CR_C_HI_IO;
239 	CALLBACK_FUNC(1, _8255_CR, config, CALLBACK_ARG);
240 }
241 
subdev_8255_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)242 static int subdev_8255_cmdtest(struct comedi_device *dev,
243 			       struct comedi_subdevice *s,
244 			       struct comedi_cmd *cmd)
245 {
246 	int err = 0;
247 	unsigned int tmp;
248 
249 	/* step 1 */
250 
251 	tmp = cmd->start_src;
252 	cmd->start_src &= TRIG_NOW;
253 	if (!cmd->start_src || tmp != cmd->start_src)
254 		err++;
255 
256 	tmp = cmd->scan_begin_src;
257 	cmd->scan_begin_src &= TRIG_EXT;
258 	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
259 		err++;
260 
261 	tmp = cmd->convert_src;
262 	cmd->convert_src &= TRIG_FOLLOW;
263 	if (!cmd->convert_src || tmp != cmd->convert_src)
264 		err++;
265 
266 	tmp = cmd->scan_end_src;
267 	cmd->scan_end_src &= TRIG_COUNT;
268 	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
269 		err++;
270 
271 	tmp = cmd->stop_src;
272 	cmd->stop_src &= TRIG_NONE;
273 	if (!cmd->stop_src || tmp != cmd->stop_src)
274 		err++;
275 
276 	if (err)
277 		return 1;
278 
279 	/* step 2 */
280 
281 	if (err)
282 		return 2;
283 
284 	/* step 3 */
285 
286 	if (cmd->start_arg != 0) {
287 		cmd->start_arg = 0;
288 		err++;
289 	}
290 	if (cmd->scan_begin_arg != 0) {
291 		cmd->scan_begin_arg = 0;
292 		err++;
293 	}
294 	if (cmd->convert_arg != 0) {
295 		cmd->convert_arg = 0;
296 		err++;
297 	}
298 	if (cmd->scan_end_arg != 1) {
299 		cmd->scan_end_arg = 1;
300 		err++;
301 	}
302 	if (cmd->stop_arg != 0) {
303 		cmd->stop_arg = 0;
304 		err++;
305 	}
306 
307 	if (err)
308 		return 3;
309 
310 	/* step 4 */
311 
312 	if (err)
313 		return 4;
314 
315 	return 0;
316 }
317 
subdev_8255_cmd(struct comedi_device * dev,struct comedi_subdevice * s)318 static int subdev_8255_cmd(struct comedi_device *dev,
319 			   struct comedi_subdevice *s)
320 {
321 	/* FIXME */
322 
323 	return 0;
324 }
325 
subdev_8255_cancel(struct comedi_device * dev,struct comedi_subdevice * s)326 static int subdev_8255_cancel(struct comedi_device *dev,
327 			      struct comedi_subdevice *s)
328 {
329 	/* FIXME */
330 
331 	return 0;
332 }
333 
subdev_8255_init(struct comedi_device * dev,struct comedi_subdevice * s,int (* cb)(int,int,int,unsigned long),unsigned long arg)334 int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s,
335 		     int (*cb) (int, int, int, unsigned long),
336 		     unsigned long arg)
337 {
338 	s->type = COMEDI_SUBD_DIO;
339 	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
340 	s->n_chan = 24;
341 	s->range_table = &range_digital;
342 	s->maxdata = 1;
343 
344 	s->private = kmalloc(sizeof(struct subdev_8255_struct), GFP_KERNEL);
345 	if (!s->private)
346 		return -ENOMEM;
347 
348 	CALLBACK_ARG = arg;
349 	if (cb == NULL)
350 		CALLBACK_FUNC = subdev_8255_cb;
351 	else
352 		CALLBACK_FUNC = cb;
353 	s->insn_bits = subdev_8255_insn;
354 	s->insn_config = subdev_8255_insn_config;
355 
356 	s->state = 0;
357 	s->io_bits = 0;
358 	do_config(dev, s);
359 
360 	return 0;
361 }
362 EXPORT_SYMBOL(subdev_8255_init);
363 
subdev_8255_init_irq(struct comedi_device * dev,struct comedi_subdevice * s,int (* cb)(int,int,int,unsigned long),unsigned long arg)364 int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s,
365 			 int (*cb) (int, int, int, unsigned long),
366 			 unsigned long arg)
367 {
368 	int ret;
369 
370 	ret = subdev_8255_init(dev, s, cb, arg);
371 	if (ret < 0)
372 		return ret;
373 
374 	s->do_cmdtest = subdev_8255_cmdtest;
375 	s->do_cmd = subdev_8255_cmd;
376 	s->cancel = subdev_8255_cancel;
377 
378 	subdevpriv->have_irq = 1;
379 
380 	return 0;
381 }
382 EXPORT_SYMBOL(subdev_8255_init_irq);
383 
subdev_8255_cleanup(struct comedi_device * dev,struct comedi_subdevice * s)384 void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s)
385 {
386 	kfree(s->private);
387 }
388 EXPORT_SYMBOL(subdev_8255_cleanup);
389 
390 /*
391 
392    Start of the 8255 standalone device
393 
394  */
395 
dev_8255_attach(struct comedi_device * dev,struct comedi_devconfig * it)396 static int dev_8255_attach(struct comedi_device *dev,
397 			   struct comedi_devconfig *it)
398 {
399 	int ret;
400 	unsigned long iobase;
401 	int i;
402 
403 	dev->board_name = "8255";
404 
405 	for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
406 		iobase = it->options[i];
407 		if (!iobase)
408 			break;
409 	}
410 	if (i == 0) {
411 		printk(KERN_WARNING
412 		       "comedi%d: 8255: no devices specified\n", dev->minor);
413 		return -EINVAL;
414 	}
415 
416 	ret = alloc_subdevices(dev, i);
417 	if (ret < 0) {
418 		/* FIXME this printk call should give a proper message, the
419 		 * below line just maintains previous functionality */
420 		printk("comedi%d: 8255:", dev->minor);
421 		return ret;
422 	}
423 
424 	printk(KERN_INFO "comedi%d: 8255:", dev->minor);
425 
426 	for (i = 0; i < dev->n_subdevices; i++) {
427 		iobase = it->options[i];
428 
429 		printk(" 0x%04lx", iobase);
430 		if (!request_region(iobase, _8255_SIZE, "8255")) {
431 			printk(" (I/O port conflict)");
432 
433 			dev->subdevices[i].type = COMEDI_SUBD_UNUSED;
434 		} else {
435 			subdev_8255_init(dev, dev->subdevices + i, NULL,
436 					 iobase);
437 		}
438 	}
439 
440 	printk("\n");
441 
442 	return 0;
443 }
444 
dev_8255_detach(struct comedi_device * dev)445 static int dev_8255_detach(struct comedi_device *dev)
446 {
447 	int i;
448 	unsigned long iobase;
449 	struct comedi_subdevice *s;
450 
451 	printk(KERN_INFO "comedi%d: 8255: remove\n", dev->minor);
452 
453 	for (i = 0; i < dev->n_subdevices; i++) {
454 		s = dev->subdevices + i;
455 		if (s->type != COMEDI_SUBD_UNUSED) {
456 			iobase = CALLBACK_ARG;
457 			release_region(iobase, _8255_SIZE);
458 		}
459 		subdev_8255_cleanup(dev, s);
460 	}
461 
462 	return 0;
463 }
464 
465 MODULE_AUTHOR("Comedi http://www.comedi.org");
466 MODULE_DESCRIPTION("Comedi low-level driver");
467 MODULE_LICENSE("GPL");
468