forked from Minki/linux
Staging: comedi: add pcmda12 driver
Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board. From: Calin Culianu <calin@ajvar.org> Cc: David Schleef <ds@schleef.org> Cc: Frank Mori Hess <fmhess@users.sourceforge.net> Cc: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
03aef4b6dc
commit
647d8b4542
304
drivers/staging/comedi/drivers/pcmda12.c
Normal file
304
drivers/staging/comedi/drivers/pcmda12.c
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
comedi/drivers/pcmda12.c
|
||||
Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board.
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 2006 Calin A. Culianu <calin@ajvar.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: pcmda12
|
||||
Description: A driver for the Winsystems PCM-D/A-12
|
||||
Devices: [Winsystems] PCM-D/A-12 (pcmda12)
|
||||
Author: Calin Culianu <calin@ajvar.org>
|
||||
Updated: Fri, 13 Jan 2006 12:01:01 -0500
|
||||
Status: works
|
||||
|
||||
A driver for the relatively straightforward-to-program PCM-D/A-12.
|
||||
This board doesn't support commands, and the only way to set its
|
||||
analog output range is to jumper the board. As such,
|
||||
comedi_data_write() ignores the range value specified.
|
||||
|
||||
The board uses 16 consecutive I/O addresses starting at the I/O port
|
||||
base address. Each address corresponds to the LSB then MSB of a
|
||||
particular channel from 0-7.
|
||||
|
||||
Note that the board is not ISA-PNP capable and thus
|
||||
needs the I/O port comedi_config parameter.
|
||||
|
||||
Note that passing a nonzero value as the second config option will
|
||||
enable "simultaneous xfer" mode for this board, in which AO writes
|
||||
will not take effect until a subsequent read of any AO channel. This
|
||||
is so that one can speed up programming by preloading all AO registers
|
||||
with values before simultaneously setting them to take effect with one
|
||||
read command.
|
||||
|
||||
Configuration Options:
|
||||
[0] - I/O port base address
|
||||
[1] - Do Simultaneous Xfer (see description)
|
||||
*/
|
||||
|
||||
#include "../comedidev.h"
|
||||
|
||||
#include <linux/pci.h> /* for PCI devices */
|
||||
|
||||
#define MIN(a,b) ( ((a) < (b)) ? (a) : (b) )
|
||||
#define SDEV_NO ((int)(s - dev->subdevices))
|
||||
#define CHANS 8
|
||||
#define IOSIZE 16
|
||||
#define LSB(x) ((unsigned char)((x) & 0xff))
|
||||
#define MSB(x) ((unsigned char)((((unsigned short)(x))>>8) & 0xff))
|
||||
#define LSB_PORT(chan) (dev->iobase + (chan)*2)
|
||||
#define MSB_PORT(chan) (LSB_PORT(chan)+1)
|
||||
#define BITS 12
|
||||
|
||||
/*
|
||||
* Bords
|
||||
*/
|
||||
typedef struct pcmda12_board_struct {
|
||||
const char *name;
|
||||
} pcmda12_board;
|
||||
|
||||
/* note these have no effect and are merely here for reference..
|
||||
these are configured by jumpering the board! */
|
||||
static const comedi_lrange pcmda12_ranges = {
|
||||
3,
|
||||
{
|
||||
UNI_RANGE(5), UNI_RANGE(10), BIP_RANGE(5)
|
||||
}
|
||||
};
|
||||
|
||||
static const pcmda12_board pcmda12_boards[] = {
|
||||
{
|
||||
name: "pcmda12",
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Useful for shorthand access to the particular board structure
|
||||
*/
|
||||
#define thisboard ((const pcmda12_board *)dev->board_ptr)
|
||||
|
||||
typedef struct {
|
||||
lsampl_t ao_readback[CHANS];
|
||||
int simultaneous_xfer_mode;
|
||||
} pcmda12_private;
|
||||
|
||||
#define devpriv ((pcmda12_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 pcmda12_attach(comedi_device * dev, comedi_devconfig * it);
|
||||
static int pcmda12_detach(comedi_device * dev);
|
||||
|
||||
static void zero_chans(comedi_device * dev);
|
||||
|
||||
static comedi_driver driver = {
|
||||
driver_name:"pcmda12",
|
||||
module:THIS_MODULE,
|
||||
attach:pcmda12_attach,
|
||||
detach:pcmda12_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 pcmda12_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:&pcmda12_boards[0].name,
|
||||
offset:sizeof(pcmda12_board),
|
||||
num_names:sizeof(pcmda12_boards) / sizeof(pcmda12_board),
|
||||
};
|
||||
|
||||
static int ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int ao_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
|
||||
/*
|
||||
* 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 pcmda12_attach(comedi_device * dev, comedi_devconfig * it)
|
||||
{
|
||||
comedi_subdevice *s;
|
||||
unsigned long iobase;
|
||||
|
||||
iobase = it->options[0];
|
||||
printk("comedi%d: %s: io: %lx %s ", dev->minor, driver.driver_name,
|
||||
iobase, it->options[1] ? "simultaneous xfer mode enabled" : "");
|
||||
|
||||
if (!request_region(iobase, IOSIZE, driver.driver_name)) {
|
||||
printk("I/O port conflict\n");
|
||||
return -EIO;
|
||||
}
|
||||
dev->iobase = iobase;
|
||||
|
||||
/*
|
||||
* 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(pcmda12_private)) < 0) {
|
||||
printk("cannot allocate private data structure\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
devpriv->simultaneous_xfer_mode = it->options[1];
|
||||
|
||||
/*
|
||||
* Allocate the subdevice structures. alloc_subdevice() is a
|
||||
* convenient macro defined in comedidev.h.
|
||||
*
|
||||
* Allocate 2 subdevs (32 + 16 DIO lines) or 3 32 DIO subdevs for the
|
||||
* 96-channel version of the board.
|
||||
*/
|
||||
if (alloc_subdevices(dev, 1) < 0) {
|
||||
printk("cannot allocate subdevice data structures\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
s = dev->subdevices;
|
||||
s->private = NULL;
|
||||
s->maxdata = (0x1 << BITS) - 1;
|
||||
s->range_table = &pcmda12_ranges;
|
||||
s->type = COMEDI_SUBD_AO;
|
||||
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
|
||||
s->n_chan = CHANS;
|
||||
s->insn_write = &ao_winsn;
|
||||
s->insn_read = &ao_rinsn;
|
||||
|
||||
zero_chans(dev); /* clear out all the registers, basically */
|
||||
|
||||
printk("attached\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* _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 pcmda12_detach(comedi_device * dev)
|
||||
{
|
||||
printk("comedi%d: %s: remove\n", dev->minor, driver.driver_name);
|
||||
if (dev->iobase)
|
||||
release_region(dev->iobase, IOSIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void zero_chans(comedi_device * dev)
|
||||
{ /* sets up an
|
||||
ASIC chip to defaults */
|
||||
int i;
|
||||
for (i = 0; i < CHANS; ++i) {
|
||||
/* /\* do this as one instruction?? *\/ */
|
||||
/* outw(0, LSB_PORT(chan)); */
|
||||
outb(0, LSB_PORT(i));
|
||||
outb(0, MSB_PORT(i));
|
||||
}
|
||||
inb(LSB_PORT(0)); /* update chans. */
|
||||
}
|
||||
|
||||
static int ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int i;
|
||||
int chan = CR_CHAN(insn->chanspec);
|
||||
|
||||
/* 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) {
|
||||
|
||||
/* /\* do this as one instruction?? *\/ */
|
||||
/* outw(data[i], LSB_PORT(chan)); */
|
||||
|
||||
/* Need to do this as two instructions due to 8-bit bus?? */
|
||||
/* first, load the low byte */
|
||||
outb(LSB(data[i]), LSB_PORT(chan));
|
||||
/* next, write the high byte */
|
||||
outb(MSB(data[i]), MSB_PORT(chan));
|
||||
|
||||
/* save shadow register */
|
||||
devpriv->ao_readback[chan] = data[i];
|
||||
|
||||
if (!devpriv->simultaneous_xfer_mode)
|
||||
inb(LSB_PORT(chan));
|
||||
}
|
||||
|
||||
/* return the number of samples 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->ao_readback.
|
||||
However, since this driver supports simultaneous xfer then sometimes
|
||||
this function actually accomplishes work.
|
||||
|
||||
Simultaneaous xfer mode is accomplished by loading ALL the values
|
||||
you want for AO in all the channels, then READing off one of the AO
|
||||
registers to initiate the instantaneous simultaneous update of all
|
||||
DAC outputs, which makes all AO channels update simultaneously.
|
||||
This is useful for some control applications, I would imagine.
|
||||
*/
|
||||
static int 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++) {
|
||||
if (devpriv->simultaneous_xfer_mode)
|
||||
inb(LSB_PORT(chan));
|
||||
/* read back shadow register */
|
||||
data[i] = devpriv->ao_readback[chan];
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenient macro that defines init_module() and cleanup_module(),
|
||||
* as necessary.
|
||||
*/
|
||||
COMEDI_INITCLEANUP(driver);
|
Loading…
Reference in New Issue
Block a user