/* comedi/drivers/skel.c Skeleton code for a Comedi driver COMEDI - Linux Control and Measurement Device Interface Copyright (C) 2000 David A. Schleef 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: skel Description: Skeleton driver, an example for driver writers Devices: Author: ds Updated: Mon, 18 Mar 2002 15:34:01 -0800 Status: works This driver is a documented example on how Comedi drivers are written. Configuration Options: none */ /* * The previous block comment is used to automatically generate * documentation in Comedi and Comedilib. The fields: * * Driver: the name of the driver * Description: a short phrase describing the driver. Don't list boards. * Devices: a full list of the boards that attempt to be supported by * the driver. Format is "(manufacturer) board name [comedi name]", * where comedi_name is the name that is used to configure the board. * See the comment near board_name: in the comedi_driver structure * below. If (manufacturer) or [comedi name] is missing, the previous * value is used. * Author: you * Updated: date when the _documentation_ was last updated. Use 'date -R' * to get a value for this. * Status: a one-word description of the status. Valid values are: * works - driver works correctly on most boards supported, and * passes comedi_test. * unknown - unknown. Usually put there by ds. * experimental - may not work in any particular release. Author * probably wants assistance testing it. * bitrotten - driver has not been update in a long time, probably * doesn't work, and probably is missing support for significant * Comedi interface features. * untested - author probably wrote it "blind", and is believed to * work, but no confirmation. * * These headers should be followed by a blank line, and any comments * you wish to say about the driver. The comment area is the place * to put any known bugs, limitations, unsupported features, supported * command triggers, whether or not commands are supported on particular * subdevices, etc. * * Somewhere in the comment should be information about configuration * options that are used with comedi_config. */ #include "../comedidev.h" #include /* for PCI devices */ /* Imaginary registers for the imaginary board */ #define SKEL_SIZE 0 #define SKEL_START_AI_CONV 0 #define SKEL_AI_READ 0 /* * Board descriptions for two imaginary boards. Describing the * boards in this way is optional, and completely driver-dependent. * Some drivers use arrays such as this, other do not. */ typedef struct skel_board_struct { const char *name; int ai_chans; int ai_bits; int have_dio; } skel_board; static const skel_board skel_boards[] = { { name: "skel-100", ai_chans:16, ai_bits: 12, have_dio:1, }, { name: "skel-200", ai_chans:8, ai_bits: 16, have_dio:0, }, }; /* This is used by modprobe to translate PCI IDs to drivers. Should * only be used for PCI and ISA-PnP devices */ /* Please add your PCI vendor ID to comedidev.h, and it will be forwarded * upstream. */ #define PCI_VENDOR_ID_SKEL 0xdafe static DEFINE_PCI_DEVICE_TABLE(skel_pci_table) = { {PCI_VENDOR_ID_SKEL, 0x0100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {PCI_VENDOR_ID_SKEL, 0x0200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {0} }; MODULE_DEVICE_TABLE(pci, skel_pci_table); /* * Useful for shorthand access to the particular board structure */ #define thisboard ((const skel_board *)dev->board_ptr) /* this structure is for data unique to this hardware driver. If several hardware drivers keep similar information in this structure, feel free to suggest moving the variable to the comedi_device struct. */ typedef struct { int data; /* would be useful for a PCI device */ struct pci_dev *pci_dev; /* Used for AO readback */ lsampl_t ao_readback[2]; } skel_private; /* * most drivers define the following macro to make it easy to * access the private structure. */ #define devpriv ((skel_private *)dev->private) /* * The comedi_driver structure tells the Comedi core module * which functions to call to configure/deconfigure (attach/detach) * the board, and also about the kernel module that contains * the device code. */ static int skel_attach(comedi_device * dev, comedi_devconfig * it); static int skel_detach(comedi_device * dev); static comedi_driver driver_skel = { driver_name:"dummy", module:THIS_MODULE, attach:skel_attach, detach:skel_detach, /* It is not necessary to implement the following members if you are * writing a driver for a ISA PnP or PCI card */ /* Most drivers will support multiple types of boards by * having an array of board structures. These were defined * in skel_boards[] above. Note that the element 'name' * was first in the structure -- Comedi uses this fact to * extract the name of the board without knowing any details * about the structure except for its length. * When a device is attached (by comedi_config), the name * of the device is given to Comedi, and Comedi tries to * match it by going through the list of board names. If * there is a match, the address of the pointer is put * into dev->board_ptr and driver->attach() is called. * * Note that these are not necessary if you can determine * the type of board in software. ISA PnP, PCI, and PCMCIA * devices are such boards. */ board_name:&skel_boards[0].name, offset:sizeof(skel_board), num_names:sizeof(skel_boards) / sizeof(skel_board), }; static int skel_ai_rinsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); static int skel_ao_winsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); static int skel_ao_rinsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); static int skel_dio_insn_bits(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); static int skel_dio_insn_config(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); static int skel_ai_cmdtest(comedi_device * dev, comedi_subdevice * s, comedi_cmd * cmd); static int skel_ns_to_timer(unsigned int *ns, int round); /* * Attach is called by the Comedi core to configure the driver * for a particular board. If you specified a board_name array * in the driver structure, dev->board_ptr contains that * address. */ static int skel_attach(comedi_device * dev, comedi_devconfig * it) { comedi_subdevice *s; printk("comedi%d: skel: ", dev->minor); /* * If you can probe the device to determine what device in a series * it is, this is the place to do it. Otherwise, dev->board_ptr * should already be initialized. */ //dev->board_ptr = skel_probe(dev, it); /* * Initialize dev->board_name. Note that we can use the "thisboard" * macro now, since we just initialized it in the last line. */ dev->board_name = thisboard->name; /* * Allocate the private structure area. alloc_private() is a * convenient macro defined in comedidev.h. */ if (alloc_private(dev, sizeof(skel_private)) < 0) return -ENOMEM; /* * Allocate the subdevice structures. alloc_subdevice() is a * convenient macro defined in comedidev.h. */ if (alloc_subdevices(dev, 3) < 0) return -ENOMEM; s = dev->subdevices + 0; //dev->read_subdev=s; /* analog input subdevice */ s->type = COMEDI_SUBD_AI; /* we support single-ended (ground) and differential */ s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; s->n_chan = thisboard->ai_chans; s->maxdata = (1 << thisboard->ai_bits) - 1; s->range_table = &range_bipolar10; s->len_chanlist = 16; /* This is the maximum chanlist length that the board can handle */ s->insn_read = skel_ai_rinsn; // s->subdev_flags |= SDF_CMD_READ; // s->do_cmd = skel_ai_cmd; s->do_cmdtest = skel_ai_cmdtest; s = dev->subdevices + 1; /* analog output subdevice */ s->type = COMEDI_SUBD_AO; s->subdev_flags = SDF_WRITABLE; s->n_chan = 1; s->maxdata = 0xffff; s->range_table = &range_bipolar5; s->insn_write = skel_ao_winsn; s->insn_read = skel_ao_rinsn; s = dev->subdevices + 2; /* digital i/o subdevice */ if (thisboard->have_dio) { s->type = COMEDI_SUBD_DIO; s->subdev_flags = SDF_READABLE | SDF_WRITABLE; s->n_chan = 16; s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = skel_dio_insn_bits; s->insn_config = skel_dio_insn_config; } else { s->type = COMEDI_SUBD_UNUSED; } printk("attached\n"); return 0; } /* * _detach is called to deconfigure a device. It should deallocate * resources. * This function is also called when _attach() fails, so it should be * careful not to release resources that were not necessarily * allocated by _attach(). dev->private and dev->subdevices are * deallocated automatically by the core. */ static int skel_detach(comedi_device * dev) { printk("comedi%d: skel: remove\n", dev->minor); return 0; } /* * "instructions" read/write data in "one-shot" or "software-triggered" * mode. */ static int skel_ai_rinsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) { int n, i; unsigned int d; unsigned int status; /* a typical programming sequence */ /* write channel to multiplexer */ //outw(chan,dev->iobase + SKEL_MUX); /* don't wait for mux to settle */ /* convert n samples */ for (n = 0; n < insn->n; n++) { /* trigger conversion */ //outw(0,dev->iobase + SKEL_CONVERT); #define TIMEOUT 100 /* wait for conversion to end */ for (i = 0; i < TIMEOUT; i++) { status = 1; //status = inb(dev->iobase + SKEL_STATUS); if (status) break; } if (i == TIMEOUT) { /* rt_printk() should be used instead of printk() * whenever the code can be called from real-time. */ rt_printk("timeout\n"); return -ETIMEDOUT; } /* read data */ //d = inw(dev->iobase + SKEL_AI_DATA); d = 0; /* mangle the data as necessary */ d ^= 1 << (thisboard->ai_bits - 1); data[n] = d; } /* return the number of samples read/written */ return n; } static int skel_ai_cmdtest(comedi_device * dev, comedi_subdevice * s, comedi_cmd * cmd) { int err = 0; int tmp; /* cmdtest tests a particular command to see if it is valid. * Using the cmdtest ioctl, a user can create a valid cmd * and then have it executes by the cmd ioctl. * * cmdtest returns 1,2,3,4 or 0, depending on which tests * the command passes. */ /* step 1: make sure trigger sources are trivially valid */ 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_TIMER | TRIG_EXT; if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++; tmp = cmd->convert_src; cmd->convert_src &= TRIG_TIMER | TRIG_EXT; 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_COUNT | TRIG_NONE; if (!cmd->stop_src || tmp != cmd->stop_src) err++; if (err) return 1; /* step 2: make sure trigger sources are unique and mutually compatible */ /* note that mutual compatiblity is not an issue here */ if (cmd->scan_begin_src != TRIG_TIMER && cmd->scan_begin_src != TRIG_EXT) err++; if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) err++; if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) err++; if (err) return 2; /* step 3: make sure arguments are trivially compatible */ if (cmd->start_arg != 0) { cmd->start_arg = 0; err++; } #define MAX_SPEED 10000 /* in nanoseconds */ #define MIN_SPEED 1000000000 /* in nanoseconds */ if (cmd->scan_begin_src == TRIG_TIMER) { if (cmd->scan_begin_arg < MAX_SPEED) { cmd->scan_begin_arg = MAX_SPEED; err++; } if (cmd->scan_begin_arg > MIN_SPEED) { cmd->scan_begin_arg = MIN_SPEED; err++; } } else { /* external trigger */ /* should be level/edge, hi/lo specification here */ /* should specify multiple external triggers */ if (cmd->scan_begin_arg > 9) { cmd->scan_begin_arg = 9; err++; } } if (cmd->convert_src == TRIG_TIMER) { if (cmd->convert_arg < MAX_SPEED) { cmd->convert_arg = MAX_SPEED; err++; } if (cmd->convert_arg > MIN_SPEED) { cmd->convert_arg = MIN_SPEED; err++; } } else { /* external trigger */ /* see above */ if (cmd->convert_arg > 9) { cmd->convert_arg = 9; err++; } } if (cmd->scan_end_arg != cmd->chanlist_len) { cmd->scan_end_arg = cmd->chanlist_len; err++; } if (cmd->stop_src == TRIG_COUNT) { if (cmd->stop_arg > 0x00ffffff) { cmd->stop_arg = 0x00ffffff; err++; } } else { /* TRIG_NONE */ if (cmd->stop_arg != 0) { cmd->stop_arg = 0; err++; } } if (err) return 3; /* step 4: fix up any arguments */ if (cmd->scan_begin_src == TRIG_TIMER) { tmp = cmd->scan_begin_arg; skel_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); if (tmp != cmd->scan_begin_arg) err++; } if (cmd->convert_src == TRIG_TIMER) { tmp = cmd->convert_arg; skel_ns_to_timer(&cmd->convert_arg, cmd->flags & TRIG_ROUND_MASK); if (tmp != cmd->convert_arg) err++; if (cmd->scan_begin_src == TRIG_TIMER && cmd->scan_begin_arg < cmd->convert_arg * cmd->scan_end_arg) { cmd->scan_begin_arg = cmd->convert_arg * cmd->scan_end_arg; err++; } } if (err) return 4; return 0; } /* This function doesn't require a particular form, this is just * what happens to be used in some of the drivers. It should * convert ns nanoseconds to a counter value suitable for programming * the device. Also, it should adjust ns so that it cooresponds to * the actual time that the device will use. */ static int skel_ns_to_timer(unsigned int *ns, int round) { /* trivial timer */ /* if your timing is done through two cascaded timers, the * i8253_cascade_ns_to_timer() function in 8253.h can be * very helpful. There are also i8254_load() and i8254_mm_load() * which can be used to load values into the ubiquitous 8254 counters */ return *ns; } static int skel_ao_winsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) { int i; int chan = CR_CHAN(insn->chanspec); printk("skel_ao_winsn\n"); /* Writing a list of values to an AO channel is probably not * very useful, but that's how the interface is defined. */ for (i = 0; i < insn->n; i++) { /* a typical programming sequence */ //outw(data[i],dev->iobase + SKEL_DA0 + chan); devpriv->ao_readback[chan] = data[i]; } /* return the number of samples read/written */ return i; } /* AO subdevices should have a read insn as well as a write insn. * Usually this means copying a value stored in devpriv. */ static int skel_ao_rinsn(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) { int i; int chan = CR_CHAN(insn->chanspec); for (i = 0; i < insn->n; i++) data[i] = devpriv->ao_readback[chan]; return i; } /* DIO devices are slightly special. Although it is possible to * implement the insn_read/insn_write interface, it is much more * useful to applications if you implement the insn_bits interface. * This allows packed reading/writing of the DIO channels. The * comedi core can convert between insn_bits and insn_read/write */ static int skel_dio_insn_bits(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) { if (insn->n != 2) return -EINVAL; /* The insn data is a mask in data[0] and the new data * in data[1], each channel cooresponding to a bit. */ if (data[0]) { s->state &= ~data[0]; s->state |= data[0] & data[1]; /* Write out the new digital output lines */ //outw(s->state,dev->iobase + SKEL_DIO); } /* on return, data[1] contains the value of the digital * input and output lines. */ //data[1]=inw(dev->iobase + SKEL_DIO); /* or we could just return the software copy of the output values if * it was a purely digital output subdevice */ //data[1]=s->state; return 2; } static int skel_dio_insn_config(comedi_device * dev, comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) { int chan = CR_CHAN(insn->chanspec); /* The input or output configuration of each digital line is * configured by a special insn_config instruction. chanspec * contains the channel to be changed, and data[0] contains the * value COMEDI_INPUT or COMEDI_OUTPUT. */ switch (data[0]) { case INSN_CONFIG_DIO_OUTPUT: s->io_bits |= 1 << chan; break; case INSN_CONFIG_DIO_INPUT: s->io_bits &= ~(1 << chan); break; case INSN_CONFIG_DIO_QUERY: data[1] = (s-> io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; return insn->n; break; default: return -EINVAL; break; } //outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); return insn->n; } /* * A convenient macro that defines init_module() and cleanup_module(), * as necessary. */ COMEDI_INITCLEANUP(driver_skel); /* If you are writing a PCI driver you should use COMEDI_PCI_INITCLEANUP instead. */ // COMEDI_PCI_INITCLEANUP(driver_skel, skel_pci_table)