Commit | Line | Data |
---|---|---|
6ca27334 DS |
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> | |
5a0e3ad6 | 84 | #include <linux/slab.h> |
c5efe58b | 85 | #include "8255.h" |
6ca27334 DS |
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 | ||
0a85b6f0 MT |
110 | static int dev_8255_attach(struct comedi_device *dev, |
111 | struct comedi_devconfig *it); | |
71b5f4f1 | 112 | static int dev_8255_detach(struct comedi_device *dev); |
139dfbdf | 113 | static struct comedi_driver driver_8255 = { |
68c3dbff BP |
114 | .driver_name = "8255", |
115 | .module = THIS_MODULE, | |
116 | .attach = dev_8255_attach, | |
117 | .detach = dev_8255_detach, | |
6ca27334 DS |
118 | }; |
119 | ||
7114a280 AT |
120 | static int __init driver_8255_init_module(void) |
121 | { | |
122 | return comedi_driver_register(&driver_8255); | |
123 | } | |
124 | ||
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); | |
6ca27334 | 132 | |
0a85b6f0 | 133 | static void do_config(struct comedi_device *dev, struct comedi_subdevice *s); |
6ca27334 | 134 | |
c5efe58b GKH |
135 | void subdev_8255_interrupt(struct comedi_device *dev, |
136 | struct comedi_subdevice *s) | |
6ca27334 | 137 | { |
790c5541 | 138 | short d; |
6ca27334 DS |
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 | } | |
228ec340 | 148 | EXPORT_SYMBOL(subdev_8255_interrupt); |
6ca27334 DS |
149 | |
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 | ||
0a85b6f0 MT |
162 | static int subdev_8255_insn(struct comedi_device *dev, |
163 | struct comedi_subdevice *s, | |
164 | struct comedi_insn *insn, unsigned int *data) | |
6ca27334 DS |
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, | |
0a85b6f0 | 172 | CALLBACK_ARG); |
6ca27334 DS |
173 | if (data[0] & 0xff00) |
174 | CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff, | |
0a85b6f0 | 175 | CALLBACK_ARG); |
6ca27334 DS |
176 | if (data[0] & 0xff0000) |
177 | CALLBACK_FUNC(1, _8255_DATA + 2, | |
0a85b6f0 | 178 | (s->state >> 16) & 0xff, CALLBACK_ARG); |
6ca27334 DS |
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 | ||
0a85b6f0 MT |
188 | static int subdev_8255_insn_config(struct comedi_device *dev, |
189 | struct comedi_subdevice *s, | |
190 | struct comedi_insn *insn, unsigned int *data) | |
6ca27334 DS |
191 | { |
192 | unsigned int mask; | |
193 | unsigned int bits; | |
194 | ||
195 | mask = 1 << CR_CHAN(insn->chanspec); | |
228ec340 | 196 | if (mask & 0x0000ff) |
6ca27334 | 197 | bits = 0x0000ff; |
228ec340 | 198 | else if (mask & 0x00ff00) |
6ca27334 | 199 | bits = 0x00ff00; |
228ec340 | 200 | else if (mask & 0x0f0000) |
6ca27334 | 201 | bits = 0x0f0000; |
228ec340 | 202 | else |
6ca27334 | 203 | bits = 0xf00000; |
6ca27334 DS |
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 | ||
0a85b6f0 | 225 | static void do_config(struct comedi_device *dev, struct comedi_subdevice *s) |
6ca27334 DS |
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 | ||
0a85b6f0 MT |
242 | static int subdev_8255_cmdtest(struct comedi_device *dev, |
243 | struct comedi_subdevice *s, | |
244 | struct comedi_cmd *cmd) | |
6ca27334 DS |
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 | ||
0a85b6f0 MT |
318 | static int subdev_8255_cmd(struct comedi_device *dev, |
319 | struct comedi_subdevice *s) | |
6ca27334 DS |
320 | { |
321 | /* FIXME */ | |
322 | ||
323 | return 0; | |
324 | } | |
325 | ||
0a85b6f0 MT |
326 | static int subdev_8255_cancel(struct comedi_device *dev, |
327 | struct comedi_subdevice *s) | |
6ca27334 DS |
328 | { |
329 | /* FIXME */ | |
330 | ||
331 | return 0; | |
332 | } | |
333 | ||
c5efe58b GKH |
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) | |
6ca27334 DS |
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; | |
228ec340 | 349 | if (cb == NULL) |
6ca27334 | 350 | CALLBACK_FUNC = subdev_8255_cb; |
228ec340 | 351 | else |
6ca27334 | 352 | CALLBACK_FUNC = cb; |
6ca27334 DS |
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 | } | |
228ec340 | 362 | EXPORT_SYMBOL(subdev_8255_init); |
6ca27334 | 363 | |
c5efe58b GKH |
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) | |
6ca27334 DS |
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 | } | |
228ec340 | 382 | EXPORT_SYMBOL(subdev_8255_init_irq); |
6ca27334 | 383 | |
c5efe58b | 384 | void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice *s) |
6ca27334 DS |
385 | { |
386 | if (s->private) { | |
c4d30ee8 BP |
387 | /* this test does nothing, so comment it out |
388 | * if (subdevpriv->have_irq) { | |
389 | * } | |
390 | */ | |
6ca27334 DS |
391 | |
392 | kfree(s->private); | |
393 | } | |
394 | } | |
228ec340 | 395 | EXPORT_SYMBOL(subdev_8255_cleanup); |
6ca27334 DS |
396 | |
397 | /* | |
398 | ||
399 | Start of the 8255 standalone device | |
400 | ||
401 | */ | |
402 | ||
0a85b6f0 MT |
403 | static int dev_8255_attach(struct comedi_device *dev, |
404 | struct comedi_devconfig *it) | |
6ca27334 DS |
405 | { |
406 | int ret; | |
407 | unsigned long iobase; | |
408 | int i; | |
409 | ||
6ca27334 DS |
410 | dev->board_name = "8255"; |
411 | ||
412 | for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) { | |
413 | iobase = it->options[i]; | |
414 | if (!iobase) | |
415 | break; | |
416 | } | |
417 | if (i == 0) { | |
02638697 M |
418 | printk(KERN_WARNING |
419 | "comedi%d: 8255: no devices specified\n", dev->minor); | |
6ca27334 DS |
420 | return -EINVAL; |
421 | } | |
422 | ||
c3744138 | 423 | ret = alloc_subdevices(dev, i); |
02638697 M |
424 | if (ret < 0) { |
425 | /* FIXME this printk call should give a proper message, the | |
426 | * below line just maintains previous functionality */ | |
427 | printk("comedi%d: 8255:", dev->minor); | |
6ca27334 | 428 | return ret; |
02638697 M |
429 | } |
430 | ||
431 | printk(KERN_INFO "comedi%d: 8255:", dev->minor); | |
6ca27334 DS |
432 | |
433 | for (i = 0; i < dev->n_subdevices; i++) { | |
434 | iobase = it->options[i]; | |
435 | ||
436 | printk(" 0x%04lx", iobase); | |
437 | if (!request_region(iobase, _8255_SIZE, "8255")) { | |
438 | printk(" (I/O port conflict)"); | |
439 | ||
440 | dev->subdevices[i].type = COMEDI_SUBD_UNUSED; | |
441 | } else { | |
442 | subdev_8255_init(dev, dev->subdevices + i, NULL, | |
0a85b6f0 | 443 | iobase); |
6ca27334 DS |
444 | } |
445 | } | |
446 | ||
447 | printk("\n"); | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
71b5f4f1 | 452 | static int dev_8255_detach(struct comedi_device *dev) |
6ca27334 DS |
453 | { |
454 | int i; | |
455 | unsigned long iobase; | |
34c43922 | 456 | struct comedi_subdevice *s; |
6ca27334 | 457 | |
02638697 | 458 | printk(KERN_INFO "comedi%d: 8255: remove\n", dev->minor); |
6ca27334 DS |
459 | |
460 | for (i = 0; i < dev->n_subdevices; i++) { | |
461 | s = dev->subdevices + i; | |
462 | if (s->type != COMEDI_SUBD_UNUSED) { | |
463 | iobase = CALLBACK_ARG; | |
464 | release_region(iobase, _8255_SIZE); | |
465 | } | |
466 | subdev_8255_cleanup(dev, s); | |
467 | } | |
468 | ||
469 | return 0; | |
470 | } | |
90f703d3 AT |
471 | |
472 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
473 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
474 | MODULE_LICENSE("GPL"); |