8255.c revision 90f703d30dd3e0c16ff80f35e34e511385a05ad5
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 dev->board_name = "8255"; 400 401 for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) { 402 iobase = it->options[i]; 403 if (!iobase) 404 break; 405 } 406 if (i == 0) { 407 printk(KERN_WARNING 408 "comedi%d: 8255: no devices specified\n", dev->minor); 409 return -EINVAL; 410 } 411 412 ret = alloc_subdevices(dev, i); 413 if (ret < 0) { 414 /* FIXME this printk call should give a proper message, the 415 * below line just maintains previous functionality */ 416 printk("comedi%d: 8255:", dev->minor); 417 return ret; 418 } 419 420 printk(KERN_INFO "comedi%d: 8255:", dev->minor); 421 422 for (i = 0; i < dev->n_subdevices; i++) { 423 iobase = it->options[i]; 424 425 printk(" 0x%04lx", iobase); 426 if (!request_region(iobase, _8255_SIZE, "8255")) { 427 printk(" (I/O port conflict)"); 428 429 dev->subdevices[i].type = COMEDI_SUBD_UNUSED; 430 } else { 431 subdev_8255_init(dev, dev->subdevices + i, NULL, 432 iobase); 433 } 434 } 435 436 printk("\n"); 437 438 return 0; 439} 440 441static int dev_8255_detach(struct comedi_device *dev) 442{ 443 int i; 444 unsigned long iobase; 445 struct comedi_subdevice *s; 446 447 printk(KERN_INFO "comedi%d: 8255: remove\n", dev->minor); 448 449 for (i = 0; i < dev->n_subdevices; i++) { 450 s = dev->subdevices + i; 451 if (s->type != COMEDI_SUBD_UNUSED) { 452 iobase = CALLBACK_ARG; 453 release_region(iobase, _8255_SIZE); 454 } 455 subdev_8255_cleanup(dev, s); 456 } 457 458 return 0; 459} 460 461MODULE_AUTHOR("Comedi http://www.comedi.org"); 462MODULE_DESCRIPTION("Comedi low-level driver"); 463MODULE_LICENSE("GPL"); 464