]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/staging/comedi/drivers/8255.c
Merge branch 'omap-pool'
[linux-2.6-omap-h63xx.git] / drivers / staging / comedi / drivers / 8255.c
diff --git a/drivers/staging/comedi/drivers/8255.c b/drivers/staging/comedi/drivers/8255.c
new file mode 100644 (file)
index 0000000..0369c7c
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+    comedi/drivers/8255.c
+    Driver for 8255
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+/*
+Driver: 8255
+Description: generic 8255 support
+Devices: [standard] 8255 (8255)
+Author: ds
+Status: works
+Updated: Fri,  7 Jun 2002 12:56:45 -0700
+
+The classic in digital I/O.  The 8255 appears in Comedi as a single
+digital I/O subdevice with 24 channels.  The channel 0 corresponds
+to the 8255's port A, bit 0; channel 23 corresponds to port C, bit
+7.  Direction configuration is done in blocks, with channels 0-7,
+8-15, 16-19, and 20-23 making up the 4 blocks.  The only 8255 mode
+supported is mode 0.
+
+You should enable compilation this driver if you plan to use a board
+that has an 8255 chip.  For multifunction boards, the main driver will
+configure the 8255 subdevice automatically.
+
+This driver also works independently with ISA and PCI cards that
+directly map the 8255 registers to I/O ports, including cards with
+multiple 8255 chips.  To configure the driver for such a card, the
+option list should be a list of the I/O port bases for each of the
+8255 chips.  For example,
+
+  comedi_config /dev/comedi0 8255 0x200,0x204,0x208,0x20c
+
+Note that most PCI 8255 boards do NOT work with this driver, and
+need a separate driver as a wrapper.  For those that do work, the
+I/O port base address can be found in the output of 'lspci -v'.
+
+*/
+
+/*
+   This file contains an exported subdevice for driving an 8255.
+
+   To use this subdevice as part of another driver, you need to
+   set up the subdevice in the attach function of the driver by
+   calling:
+
+     subdev_8255_init(device, subdevice, callback_function, arg)
+
+   device and subdevice are pointers to the device and subdevice
+   structures.  callback_function will be called to provide the
+   low-level input/output to the device, i.e., actual register
+   access.  callback_function will be called with the value of arg
+   as the last parameter.  If the 8255 device is mapped as 4
+   consecutive I/O ports, you can use NULL for callback_function
+   and the I/O port base for arg, and an internal function will
+   handle the register access.
+
+   In addition, if the main driver handles interrupts, you can
+   enable commands on the subdevice by calling subdev_8255_init_irq()
+   instead.  Then, when you get an interrupt that is likely to be
+   from the 8255, you should call subdev_8255_interrupt(), which
+   will copy the latched value to a Comedi buffer.
+ */
+
+#include "../comedidev.h"
+
+#include <linux/ioport.h>
+
+#define _8255_SIZE 4
+
+#define _8255_DATA 0
+#define _8255_CR 3
+
+#define CR_C_LO_IO     0x01
+#define CR_B_IO                0x02
+#define CR_B_MODE      0x04
+#define CR_C_HI_IO     0x08
+#define CR_A_IO                0x10
+#define CR_A_MODE(a)   ((a)<<5)
+#define CR_CW          0x80
+
+struct subdev_8255_struct {
+       unsigned long cb_arg;
+       int (*cb_func) (int, int, int, unsigned long);
+       int have_irq;
+};
+
+#define CALLBACK_ARG   (((struct subdev_8255_struct *)s->private)->cb_arg)
+#define CALLBACK_FUNC  (((struct subdev_8255_struct *)s->private)->cb_func)
+#define subdevpriv     ((struct subdev_8255_struct *)s->private)
+
+static int dev_8255_attach(struct comedi_device *dev, struct comedi_devconfig * it);
+static int dev_8255_detach(struct comedi_device *dev);
+static struct comedi_driver driver_8255 = {
+      driver_name:"8255",
+      module:THIS_MODULE,
+      attach:dev_8255_attach,
+      detach:dev_8255_detach,
+};
+
+COMEDI_INITCLEANUP(driver_8255);
+
+static void do_config(struct comedi_device *dev, struct comedi_subdevice * s);
+
+void subdev_8255_interrupt(struct comedi_device *dev, struct comedi_subdevice * s)
+{
+       short d;
+
+       d = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
+       d |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
+
+       comedi_buf_put(s->async, d);
+       s->async->events |= COMEDI_CB_EOS;
+
+       comedi_event(dev, s);
+}
+
+static int subdev_8255_cb(int dir, int port, int data, unsigned long arg)
+{
+       unsigned long iobase = arg;
+
+       if (dir) {
+               outb(data, iobase + port);
+               return 0;
+       } else {
+               return inb(iobase + port);
+       }
+}
+
+static int subdev_8255_insn(struct comedi_device *dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       if (data[0]) {
+               s->state &= ~data[0];
+               s->state |= (data[0] & data[1]);
+
+               if (data[0] & 0xff)
+                       CALLBACK_FUNC(1, _8255_DATA, s->state & 0xff,
+                               CALLBACK_ARG);
+               if (data[0] & 0xff00)
+                       CALLBACK_FUNC(1, _8255_DATA + 1, (s->state >> 8) & 0xff,
+                               CALLBACK_ARG);
+               if (data[0] & 0xff0000)
+                       CALLBACK_FUNC(1, _8255_DATA + 2,
+                               (s->state >> 16) & 0xff, CALLBACK_ARG);
+       }
+
+       data[1] = CALLBACK_FUNC(0, _8255_DATA, 0, CALLBACK_ARG);
+       data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 1, 0, CALLBACK_ARG) << 8);
+       data[1] |= (CALLBACK_FUNC(0, _8255_DATA + 2, 0, CALLBACK_ARG) << 16);
+
+       return 2;
+}
+
+static int subdev_8255_insn_config(struct comedi_device *dev, struct comedi_subdevice * s,
+       struct comedi_insn * insn, unsigned int * data)
+{
+       unsigned int mask;
+       unsigned int bits;
+
+       mask = 1 << CR_CHAN(insn->chanspec);
+       if (mask & 0x0000ff) {
+               bits = 0x0000ff;
+       } else if (mask & 0x00ff00) {
+               bits = 0x00ff00;
+       } else if (mask & 0x0f0000) {
+               bits = 0x0f0000;
+       } else {
+               bits = 0xf00000;
+       }
+
+       switch (data[0]) {
+       case INSN_CONFIG_DIO_INPUT:
+               s->io_bits &= ~bits;
+               break;
+       case INSN_CONFIG_DIO_OUTPUT:
+               s->io_bits |= bits;
+               break;
+       case INSN_CONFIG_DIO_QUERY:
+               data[1] = (s->io_bits & bits) ? COMEDI_OUTPUT : COMEDI_INPUT;
+               return insn->n;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       do_config(dev, s);
+
+       return 1;
+}
+
+static void do_config(struct comedi_device *dev, struct comedi_subdevice * s)
+{
+       int config;
+
+       config = CR_CW;
+       /* 1 in io_bits indicates output, 1 in config indicates input */
+       if (!(s->io_bits & 0x0000ff))
+               config |= CR_A_IO;
+       if (!(s->io_bits & 0x00ff00))
+               config |= CR_B_IO;
+       if (!(s->io_bits & 0x0f0000))
+               config |= CR_C_LO_IO;
+       if (!(s->io_bits & 0xf00000))
+               config |= CR_C_HI_IO;
+       CALLBACK_FUNC(1, _8255_CR, config, CALLBACK_ARG);
+}
+
+static int subdev_8255_cmdtest(struct comedi_device *dev, struct comedi_subdevice * s,
+       struct comedi_cmd * cmd)
+{
+       int err = 0;
+       unsigned int tmp;
+
+       /* step 1 */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW;
+       if (!cmd->start_src || tmp != cmd->start_src)
+               err++;
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_EXT;
+       if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
+               err++;
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_FOLLOW;
+       if (!cmd->convert_src || tmp != cmd->convert_src)
+               err++;
+
+       tmp = cmd->scan_end_src;
+       cmd->scan_end_src &= TRIG_COUNT;
+       if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
+               err++;
+
+       tmp = cmd->stop_src;
+       cmd->stop_src &= TRIG_NONE;
+       if (!cmd->stop_src || tmp != cmd->stop_src)
+               err++;
+
+       if (err)
+               return 1;
+
+       /* step 2 */
+
+       if (err)
+               return 2;
+
+       /* step 3 */
+
+       if (cmd->start_arg != 0) {
+               cmd->start_arg = 0;
+               err++;
+       }
+       if (cmd->scan_begin_arg != 0) {
+               cmd->scan_begin_arg = 0;
+               err++;
+       }
+       if (cmd->convert_arg != 0) {
+               cmd->convert_arg = 0;
+               err++;
+       }
+       if (cmd->scan_end_arg != 1) {
+               cmd->scan_end_arg = 1;
+               err++;
+       }
+       if (cmd->stop_arg != 0) {
+               cmd->stop_arg = 0;
+               err++;
+       }
+
+       if (err)
+               return 3;
+
+       /* step 4 */
+
+       if (err)
+               return 4;
+
+       return 0;
+}
+
+static int subdev_8255_cmd(struct comedi_device *dev, struct comedi_subdevice * s)
+{
+       /* FIXME */
+
+       return 0;
+}
+
+static int subdev_8255_cancel(struct comedi_device *dev, struct comedi_subdevice * s)
+{
+       /* FIXME */
+
+       return 0;
+}
+
+int subdev_8255_init(struct comedi_device *dev, struct comedi_subdevice * s, int (*cb) (int,
+               int, int, unsigned long), unsigned long arg)
+{
+       s->type = COMEDI_SUBD_DIO;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+       s->n_chan = 24;
+       s->range_table = &range_digital;
+       s->maxdata = 1;
+
+       s->private = kmalloc(sizeof(struct subdev_8255_struct), GFP_KERNEL);
+       if (!s->private)
+               return -ENOMEM;
+
+       CALLBACK_ARG = arg;
+       if (cb == NULL) {
+               CALLBACK_FUNC = subdev_8255_cb;
+       } else {
+               CALLBACK_FUNC = cb;
+       }
+       s->insn_bits = subdev_8255_insn;
+       s->insn_config = subdev_8255_insn_config;
+
+       s->state = 0;
+       s->io_bits = 0;
+       do_config(dev, s);
+
+       return 0;
+}
+
+int subdev_8255_init_irq(struct comedi_device *dev, struct comedi_subdevice * s,
+       int (*cb) (int, int, int, unsigned long), unsigned long arg)
+{
+       int ret;
+
+       ret = subdev_8255_init(dev, s, cb, arg);
+       if (ret < 0)
+               return ret;
+
+       s->do_cmdtest = subdev_8255_cmdtest;
+       s->do_cmd = subdev_8255_cmd;
+       s->cancel = subdev_8255_cancel;
+
+       subdevpriv->have_irq = 1;
+
+       return 0;
+}
+
+void subdev_8255_cleanup(struct comedi_device *dev, struct comedi_subdevice * s)
+{
+       if (s->private) {
+               if (subdevpriv->have_irq) {
+               }
+
+               kfree(s->private);
+       }
+}
+
+/*
+
+   Start of the 8255 standalone device
+
+ */
+
+static int dev_8255_attach(struct comedi_device *dev, struct comedi_devconfig * it)
+{
+       int ret;
+       unsigned long iobase;
+       int i;
+
+       printk("comedi%d: 8255:", dev->minor);
+
+       dev->board_name = "8255";
+
+       for (i = 0; i < COMEDI_NDEVCONFOPTS; i++) {
+               iobase = it->options[i];
+               if (!iobase)
+                       break;
+       }
+       if (i == 0) {
+               printk(" no devices specified\n");
+               return -EINVAL;
+       }
+
+       if ((ret = alloc_subdevices(dev, i)) < 0)
+               return ret;
+
+       for (i = 0; i < dev->n_subdevices; i++) {
+               iobase = it->options[i];
+
+               printk(" 0x%04lx", iobase);
+               if (!request_region(iobase, _8255_SIZE, "8255")) {
+                       printk(" (I/O port conflict)");
+
+                       dev->subdevices[i].type = COMEDI_SUBD_UNUSED;
+               } else {
+                       subdev_8255_init(dev, dev->subdevices + i, NULL,
+                               iobase);
+               }
+       }
+
+       printk("\n");
+
+       return 0;
+}
+
+static int dev_8255_detach(struct comedi_device *dev)
+{
+       int i;
+       unsigned long iobase;
+       struct comedi_subdevice *s;
+
+       printk("comedi%d: 8255: remove\n", dev->minor);
+
+       for (i = 0; i < dev->n_subdevices; i++) {
+               s = dev->subdevices + i;
+               if (s->type != COMEDI_SUBD_UNUSED) {
+                       iobase = CALLBACK_ARG;
+                       release_region(iobase, _8255_SIZE);
+               }
+               subdev_8255_cleanup(dev, s);
+       }
+
+       return 0;
+}
+
+EXPORT_SYMBOL(subdev_8255_init);
+EXPORT_SYMBOL(subdev_8255_init_irq);
+EXPORT_SYMBOL(subdev_8255_cleanup);
+EXPORT_SYMBOL(subdev_8255_interrupt);