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. | |
6ca27334 DS |
17 | */ |
18 | /* | |
19 | Driver: 8255 | |
20 | Description: generic 8255 support | |
21 | Devices: [standard] 8255 (8255) | |
22 | Author: ds | |
23 | Status: works | |
24 | Updated: Fri, 7 Jun 2002 12:56:45 -0700 | |
25 | ||
26 | The classic in digital I/O. The 8255 appears in Comedi as a single | |
27 | digital I/O subdevice with 24 channels. The channel 0 corresponds | |
28 | to the 8255's port A, bit 0; channel 23 corresponds to port C, bit | |
29 | 7. Direction configuration is done in blocks, with channels 0-7, | |
30 | 8-15, 16-19, and 20-23 making up the 4 blocks. The only 8255 mode | |
31 | supported is mode 0. | |
32 | ||
33 | You should enable compilation this driver if you plan to use a board | |
34 | that has an 8255 chip. For multifunction boards, the main driver will | |
35 | configure the 8255 subdevice automatically. | |
36 | ||
37 | This driver also works independently with ISA and PCI cards that | |
38 | directly map the 8255 registers to I/O ports, including cards with | |
39 | multiple 8255 chips. To configure the driver for such a card, the | |
40 | option list should be a list of the I/O port bases for each of the | |
41 | 8255 chips. For example, | |
42 | ||
43 | comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c | |
44 | ||
45 | Note that most PCI 8255 boards do NOT work with this driver, and | |
46 | need a separate driver as a wrapper. For those that do work, the | |
47 | I/O port base address can be found in the output of 'lspci -v'. | |
48 | ||
49 | */ | |
50 | ||
51 | /* | |
52 | This file contains an exported subdevice for driving an 8255. | |
53 | ||
54 | To use this subdevice as part of another driver, you need to | |
55 | set up the subdevice in the attach function of the driver by | |
56 | calling: | |
57 | ||
d29a18dc | 58 | subdev_8255_init(device, subdevice, io_function, iobase) |
6ca27334 DS |
59 | |
60 | device and subdevice are pointers to the device and subdevice | |
d29a18dc | 61 | structures. io_function will be called to provide the |
6ca27334 | 62 | low-level input/output to the device, i.e., actual register |
d29a18dc | 63 | access. io_function will be called with the value of iobase |
6ca27334 | 64 | as the last parameter. If the 8255 device is mapped as 4 |
d29a18dc HS |
65 | consecutive I/O ports, you can use NULL for io_function |
66 | and the I/O port base for iobase, and an internal function will | |
6ca27334 DS |
67 | handle the register access. |
68 | ||
69 | In addition, if the main driver handles interrupts, you can | |
70 | enable commands on the subdevice by calling subdev_8255_init_irq() | |
71 | instead. Then, when you get an interrupt that is likely to be | |
72 | from the 8255, you should call subdev_8255_interrupt(), which | |
73 | will copy the latched value to a Comedi buffer. | |
74 | */ | |
75 | ||
76 | #include "../comedidev.h" | |
77 | ||
78 | #include <linux/ioport.h> | |
5a0e3ad6 | 79 | #include <linux/slab.h> |
27020ffe HS |
80 | |
81 | #include "comedi_fc.h" | |
c5efe58b | 82 | #include "8255.h" |
6ca27334 | 83 | |
03ae8189 | 84 | #define _8255_SIZE 4 |
6ca27334 | 85 | |
03ae8189 HS |
86 | #define _8255_DATA 0 |
87 | #define _8255_CR 3 | |
6ca27334 DS |
88 | |
89 | #define CR_C_LO_IO 0x01 | |
90 | #define CR_B_IO 0x02 | |
91 | #define CR_B_MODE 0x04 | |
92 | #define CR_C_HI_IO 0x08 | |
93 | #define CR_A_IO 0x10 | |
94 | #define CR_A_MODE(a) ((a)<<5) | |
95 | #define CR_CW 0x80 | |
96 | ||
a9044d91 | 97 | struct subdev_8255_private { |
7a583163 HS |
98 | unsigned long iobase; |
99 | int (*io) (int, int, int, unsigned long); | |
6ca27334 DS |
100 | }; |
101 | ||
f9af899b HS |
102 | static int subdev_8255_io(int dir, int port, int data, unsigned long iobase) |
103 | { | |
104 | if (dir) { | |
105 | outb(data, iobase + port); | |
106 | return 0; | |
107 | } else { | |
108 | return inb(iobase + port); | |
109 | } | |
110 | } | |
111 | ||
c5efe58b GKH |
112 | void subdev_8255_interrupt(struct comedi_device *dev, |
113 | struct comedi_subdevice *s) | |
6ca27334 | 114 | { |
7c61452a | 115 | struct subdev_8255_private *spriv = s->private; |
f218d9f5 | 116 | unsigned long iobase = spriv->iobase; |
790c5541 | 117 | short d; |
6ca27334 | 118 | |
f218d9f5 HS |
119 | d = spriv->io(0, _8255_DATA, 0, iobase); |
120 | d |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8); | |
6ca27334 DS |
121 | |
122 | comedi_buf_put(s->async, d); | |
123 | s->async->events |= COMEDI_CB_EOS; | |
124 | ||
125 | comedi_event(dev, s); | |
126 | } | |
5660e742 | 127 | EXPORT_SYMBOL_GPL(subdev_8255_interrupt); |
6ca27334 | 128 | |
0a85b6f0 MT |
129 | static int subdev_8255_insn(struct comedi_device *dev, |
130 | struct comedi_subdevice *s, | |
131 | struct comedi_insn *insn, unsigned int *data) | |
6ca27334 | 132 | { |
7c61452a | 133 | struct subdev_8255_private *spriv = s->private; |
f218d9f5 | 134 | unsigned long iobase = spriv->iobase; |
459f299e HS |
135 | unsigned int mask; |
136 | unsigned int bits; | |
137 | unsigned int v; | |
7c61452a | 138 | |
459f299e HS |
139 | mask = data[0]; |
140 | bits = data[1]; | |
141 | ||
142 | if (mask) { | |
143 | v = s->state; | |
144 | v &= ~mask; | |
145 | v |= (bits & mask); | |
146 | ||
147 | if (mask & 0xff) | |
148 | spriv->io(1, _8255_DATA, v & 0xff, iobase); | |
149 | if (mask & 0xff00) | |
150 | spriv->io(1, _8255_DATA + 1, (v >> 8) & 0xff, iobase); | |
151 | if (mask & 0xff0000) | |
152 | spriv->io(1, _8255_DATA + 2, (v >> 16) & 0xff, iobase); | |
153 | ||
154 | s->state = v; | |
6ca27334 DS |
155 | } |
156 | ||
459f299e HS |
157 | v = spriv->io(0, _8255_DATA, 0, iobase); |
158 | v |= (spriv->io(0, _8255_DATA + 1, 0, iobase) << 8); | |
159 | v |= (spriv->io(0, _8255_DATA + 2, 0, iobase) << 16); | |
160 | ||
161 | data[1] = v; | |
6ca27334 | 162 | |
459f299e | 163 | return insn->n; |
6ca27334 DS |
164 | } |
165 | ||
cf8a6b3d HS |
166 | static void subdev_8255_do_config(struct comedi_device *dev, |
167 | struct comedi_subdevice *s) | |
3e699ed1 | 168 | { |
7c61452a | 169 | struct subdev_8255_private *spriv = s->private; |
f218d9f5 | 170 | unsigned long iobase = spriv->iobase; |
3e699ed1 HS |
171 | int config; |
172 | ||
173 | config = CR_CW; | |
174 | /* 1 in io_bits indicates output, 1 in config indicates input */ | |
175 | if (!(s->io_bits & 0x0000ff)) | |
176 | config |= CR_A_IO; | |
177 | if (!(s->io_bits & 0x00ff00)) | |
178 | config |= CR_B_IO; | |
179 | if (!(s->io_bits & 0x0f0000)) | |
180 | config |= CR_C_LO_IO; | |
181 | if (!(s->io_bits & 0xf00000)) | |
182 | config |= CR_C_HI_IO; | |
f218d9f5 HS |
183 | |
184 | spriv->io(1, _8255_CR, config, iobase); | |
3e699ed1 HS |
185 | } |
186 | ||
0a85b6f0 MT |
187 | static int subdev_8255_insn_config(struct comedi_device *dev, |
188 | struct comedi_subdevice *s, | |
189 | struct comedi_insn *insn, unsigned int *data) | |
6ca27334 DS |
190 | { |
191 | unsigned int mask; | |
192 | unsigned int bits; | |
193 | ||
194 | mask = 1 << CR_CHAN(insn->chanspec); | |
228ec340 | 195 | if (mask & 0x0000ff) |
6ca27334 | 196 | bits = 0x0000ff; |
228ec340 | 197 | else if (mask & 0x00ff00) |
6ca27334 | 198 | bits = 0x00ff00; |
228ec340 | 199 | else if (mask & 0x0f0000) |
6ca27334 | 200 | bits = 0x0f0000; |
228ec340 | 201 | else |
6ca27334 | 202 | bits = 0xf00000; |
6ca27334 DS |
203 | |
204 | switch (data[0]) { | |
205 | case INSN_CONFIG_DIO_INPUT: | |
206 | s->io_bits &= ~bits; | |
207 | break; | |
208 | case INSN_CONFIG_DIO_OUTPUT: | |
209 | s->io_bits |= bits; | |
210 | break; | |
211 | case INSN_CONFIG_DIO_QUERY: | |
212 | data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT; | |
213 | return insn->n; | |
214 | break; | |
215 | default: | |
216 | return -EINVAL; | |
217 | } | |
218 | ||
cf8a6b3d | 219 | subdev_8255_do_config(dev, s); |
6ca27334 DS |
220 | |
221 | return 1; | |
222 | } | |
223 | ||
0a85b6f0 MT |
224 | static int subdev_8255_cmdtest(struct comedi_device *dev, |
225 | struct comedi_subdevice *s, | |
226 | struct comedi_cmd *cmd) | |
6ca27334 DS |
227 | { |
228 | int err = 0; | |
6ca27334 | 229 | |
27020ffe | 230 | /* Step 1 : check if triggers are trivially valid */ |
6ca27334 | 231 | |
27020ffe HS |
232 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
233 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); | |
234 | err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); | |
235 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
236 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); | |
6ca27334 DS |
237 | |
238 | if (err) | |
239 | return 1; | |
240 | ||
27020ffe HS |
241 | /* Step 2a : make sure trigger sources are unique */ |
242 | /* Step 2b : and mutually compatible */ | |
6ca27334 DS |
243 | |
244 | if (err) | |
245 | return 2; | |
246 | ||
851eef80 | 247 | /* Step 3: check if arguments are trivially valid */ |
6ca27334 | 248 | |
851eef80 HS |
249 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); |
250 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
251 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); | |
252 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, 1); | |
253 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); | |
6ca27334 DS |
254 | |
255 | if (err) | |
256 | return 3; | |
257 | ||
258 | /* step 4 */ | |
259 | ||
260 | if (err) | |
261 | return 4; | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
0a85b6f0 MT |
266 | static int subdev_8255_cmd(struct comedi_device *dev, |
267 | struct comedi_subdevice *s) | |
6ca27334 DS |
268 | { |
269 | /* FIXME */ | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
0a85b6f0 MT |
274 | static int subdev_8255_cancel(struct comedi_device *dev, |
275 | struct comedi_subdevice *s) | |
6ca27334 DS |
276 | { |
277 | /* FIXME */ | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
c5efe58b | 282 | int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice *s, |
d29a18dc HS |
283 | int (*io) (int, int, int, unsigned long), |
284 | unsigned long iobase) | |
6ca27334 | 285 | { |
7c61452a HS |
286 | struct subdev_8255_private *spriv; |
287 | ||
e64fb55b | 288 | spriv = kzalloc(sizeof(*spriv), GFP_KERNEL); |
7c61452a | 289 | if (!spriv) |
6ca27334 DS |
290 | return -ENOMEM; |
291 | ||
cc31b1be HS |
292 | spriv->iobase = iobase; |
293 | spriv->io = io ? io : subdev_8255_io; | |
294 | ||
295 | s->private = spriv; | |
296 | ||
297 | s->type = COMEDI_SUBD_DIO; | |
298 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
299 | s->n_chan = 24; | |
300 | s->range_table = &range_digital; | |
301 | s->maxdata = 1; | |
302 | s->insn_bits = subdev_8255_insn; | |
303 | s->insn_config = subdev_8255_insn_config; | |
304 | ||
305 | s->state = 0; | |
306 | s->io_bits = 0; | |
6ca27334 | 307 | |
cf8a6b3d | 308 | subdev_8255_do_config(dev, s); |
6ca27334 DS |
309 | |
310 | return 0; | |
311 | } | |
5660e742 | 312 | EXPORT_SYMBOL_GPL(subdev_8255_init); |
6ca27334 | 313 | |
c5efe58b | 314 | int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice *s, |
d29a18dc HS |
315 | int (*io) (int, int, int, unsigned long), |
316 | unsigned long iobase) | |
6ca27334 DS |
317 | { |
318 | int ret; | |
319 | ||
d29a18dc | 320 | ret = subdev_8255_init(dev, s, io, iobase); |
3e189f08 | 321 | if (ret) |
6ca27334 DS |
322 | return ret; |
323 | ||
34cfcf9a HS |
324 | s->do_cmdtest = subdev_8255_cmdtest; |
325 | s->do_cmd = subdev_8255_cmd; | |
326 | s->cancel = subdev_8255_cancel; | |
6ca27334 | 327 | |
6ca27334 DS |
328 | return 0; |
329 | } | |
5660e742 | 330 | EXPORT_SYMBOL_GPL(subdev_8255_init_irq); |
6ca27334 | 331 | |
6ca27334 DS |
332 | /* |
333 | ||
334 | Start of the 8255 standalone device | |
335 | ||
336 | */ | |
337 | ||
0a85b6f0 MT |
338 | static int dev_8255_attach(struct comedi_device *dev, |
339 | struct comedi_devconfig *it) | |
6ca27334 | 340 | { |
e40e8375 | 341 | struct comedi_subdevice *s; |
6ca27334 DS |
342 | int ret; |
343 | unsigned long iobase; | |
344 | int i; | |
345 | ||
6ca27334 DS |
346 | for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) { |
347 | iobase = it->options[i]; | |
348 | if (!iobase) | |
349 | break; | |
350 | } | |
351 | if (i == 0) { | |
01bd3e3f | 352 | dev_warn(dev->class_dev, "no devices specified\n"); |
6ca27334 DS |
353 | return -EINVAL; |
354 | } | |
355 | ||
2f0b9d08 | 356 | ret = comedi_alloc_subdevices(dev, i); |
8b6c5694 | 357 | if (ret) |
6ca27334 | 358 | return ret; |
02638697 | 359 | |
6ca27334 | 360 | for (i = 0; i < dev->n_subdevices; i++) { |
5101b4d1 | 361 | s = &dev->subdevices[i]; |
6ca27334 DS |
362 | iobase = it->options[i]; |
363 | ||
e9720fd2 HS |
364 | ret = __comedi_request_region(dev, iobase, _8255_SIZE); |
365 | if (ret) { | |
e40e8375 | 366 | s->type = COMEDI_SUBD_UNUSED; |
6ca27334 | 367 | } else { |
3e189f08 HS |
368 | ret = subdev_8255_init(dev, s, NULL, iobase); |
369 | if (ret) | |
370 | return ret; | |
6ca27334 DS |
371 | } |
372 | } | |
373 | ||
6ca27334 DS |
374 | return 0; |
375 | } | |
376 | ||
484ecc95 | 377 | static void dev_8255_detach(struct comedi_device *dev) |
6ca27334 | 378 | { |
34c43922 | 379 | struct comedi_subdevice *s; |
7c61452a HS |
380 | struct subdev_8255_private *spriv; |
381 | int i; | |
6ca27334 | 382 | |
6ca27334 | 383 | for (i = 0; i < dev->n_subdevices; i++) { |
5101b4d1 | 384 | s = &dev->subdevices[i]; |
6ca27334 | 385 | if (s->type != COMEDI_SUBD_UNUSED) { |
7c61452a | 386 | spriv = s->private; |
7a583163 | 387 | release_region(spriv->iobase, _8255_SIZE); |
6ca27334 | 388 | } |
2f69915c | 389 | comedi_spriv_free(dev, i); |
6ca27334 | 390 | } |
6ca27334 | 391 | } |
90f703d3 | 392 | |
294f930d | 393 | static struct comedi_driver dev_8255_driver = { |
3e699ed1 HS |
394 | .driver_name = "8255", |
395 | .module = THIS_MODULE, | |
396 | .attach = dev_8255_attach, | |
397 | .detach = dev_8255_detach, | |
398 | }; | |
294f930d | 399 | module_comedi_driver(dev_8255_driver); |
3e699ed1 | 400 | |
90f703d3 AT |
401 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
402 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
403 | MODULE_LICENSE("GPL"); |