Staging: comedi: add cb_pcidda driver
Driver for the ComputerBoards / MeasurementComputing PCI-DDA series. From: Ivan Martinez <ivanmr@altavista.com> Cc: David Schleef <ds@schleef.org> Cc: Ian Abbott <abbotti@mev.co.uk> Cc: Frank Mori Hess <fmhess@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Родитель
88b12a9a66
Коммит
086df5b5fe
|
@ -0,0 +1,841 @@
|
|||
/*
|
||||
comedi/drivers/cb_pcidda.c
|
||||
This intends to be a driver for the ComputerBoards / MeasurementComputing
|
||||
PCI-DDA series.
|
||||
|
||||
Copyright (C) 2001 Ivan Martinez <ivanmr@altavista.com>
|
||||
Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 1997-8 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: cb_pcidda
|
||||
Description: MeasurementComputing PCI-DDA series
|
||||
Author: Ivan Martinez <ivanmr@altavista.com>, Frank Mori Hess <fmhess@users.sourceforge.net>
|
||||
Status: Supports 08/16, 04/16, 02/16, 08/12, 04/12, and 02/12
|
||||
Devices: [Measurement Computing] PCI-DDA08/12 (cb_pcidda), PCI-DDA04/12,
|
||||
PCI-DDA02/12, PCI-DDA08/16, PCI-DDA04/16, PCI-DDA02/16
|
||||
|
||||
Configuration options:
|
||||
[0] - PCI bus of device (optional)
|
||||
[1] - PCI slot of device (optional)
|
||||
If bus/slot is not specified, the first available PCI
|
||||
device will be used.
|
||||
|
||||
Only simple analog output writing is supported.
|
||||
|
||||
So far it has only been tested with:
|
||||
- PCI-DDA08/12
|
||||
Please report success/failure with other different cards to
|
||||
<comedi@comedi.org>.
|
||||
*/
|
||||
|
||||
#include "../comedidev.h"
|
||||
|
||||
#include "comedi_pci.h"
|
||||
#include "8255.h"
|
||||
|
||||
#define PCI_VENDOR_ID_CB 0x1307 // PCI vendor number of ComputerBoards
|
||||
#define N_BOARDS 10 // Number of boards in cb_pcidda_boards
|
||||
#define EEPROM_SIZE 128 // number of entries in eeprom
|
||||
#define MAX_AO_CHANNELS 8 // maximum number of ao channels for supported boards
|
||||
|
||||
/* PCI-DDA base addresses */
|
||||
#define DIGITALIO_BADRINDEX 2
|
||||
// DIGITAL I/O is pci_dev->resource[2]
|
||||
#define DIGITALIO_SIZE 8
|
||||
// DIGITAL I/O uses 8 I/O port addresses
|
||||
#define DAC_BADRINDEX 3
|
||||
// DAC is pci_dev->resource[3]
|
||||
|
||||
/* Digital I/O registers */
|
||||
#define PORT1A 0 // PORT 1A DATA
|
||||
|
||||
#define PORT1B 1 // PORT 1B DATA
|
||||
|
||||
#define PORT1C 2 // PORT 1C DATA
|
||||
|
||||
#define CONTROL1 3 // CONTROL REGISTER 1
|
||||
|
||||
#define PORT2A 4 // PORT 2A DATA
|
||||
|
||||
#define PORT2B 5 // PORT 2B DATA
|
||||
|
||||
#define PORT2C 6 // PORT 2C DATA
|
||||
|
||||
#define CONTROL2 7 // CONTROL REGISTER 2
|
||||
|
||||
/* DAC registers */
|
||||
#define DACONTROL 0 // D/A CONTROL REGISTER
|
||||
#define SU 0000001 // Simultaneous update enabled
|
||||
#define NOSU 0000000 // Simultaneous update disabled
|
||||
#define ENABLEDAC 0000002 // Enable specified DAC
|
||||
#define DISABLEDAC 0000000 // Disable specified DAC
|
||||
#define RANGE2V5 0000000 // 2.5V
|
||||
#define RANGE5V 0000200 // 5V
|
||||
#define RANGE10V 0000300 // 10V
|
||||
#define UNIP 0000400 // Unipolar outputs
|
||||
#define BIP 0000000 // Bipolar outputs
|
||||
|
||||
#define DACALIBRATION1 4 // D/A CALIBRATION REGISTER 1
|
||||
//write bits
|
||||
#define SERIAL_IN_BIT 0x1 // serial data input for eeprom, caldacs, reference dac
|
||||
#define CAL_CHANNEL_MASK (0x7 << 1)
|
||||
#define CAL_CHANNEL_BITS(channel) (((channel) << 1) & CAL_CHANNEL_MASK)
|
||||
//read bits
|
||||
#define CAL_COUNTER_MASK 0x1f
|
||||
#define CAL_COUNTER_OVERFLOW_BIT 0x20 // calibration counter overflow status bit
|
||||
#define AO_BELOW_REF_BIT 0x40 // analog output is less than reference dac voltage
|
||||
#define SERIAL_OUT_BIT 0x80 // serial data out, for reading from eeprom
|
||||
|
||||
#define DACALIBRATION2 6 // D/A CALIBRATION REGISTER 2
|
||||
#define SELECT_EEPROM_BIT 0x1 // send serial data in to eeprom
|
||||
#define DESELECT_REF_DAC_BIT 0x2 // don't send serial data to MAX542 reference dac
|
||||
#define DESELECT_CALDAC_BIT(n) (0x4 << (n)) // don't send serial data to caldac n
|
||||
#define DUMMY_BIT 0x40 // manual says to set this bit with no explanation
|
||||
|
||||
#define DADATA 8 // FIRST D/A DATA REGISTER (0)
|
||||
|
||||
static const comedi_lrange cb_pcidda_ranges = {
|
||||
6,
|
||||
{
|
||||
BIP_RANGE(10),
|
||||
BIP_RANGE(5),
|
||||
BIP_RANGE(2.5),
|
||||
UNI_RANGE(10),
|
||||
UNI_RANGE(5),
|
||||
UNI_RANGE(2.5),
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* 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 cb_pcidda_board_struct {
|
||||
const char *name;
|
||||
char status; // Driver status:
|
||||
// 0 - tested
|
||||
// 1 - manual read, not tested
|
||||
// 2 - manual not read
|
||||
unsigned short device_id;
|
||||
int ao_chans;
|
||||
int ao_bits;
|
||||
const comedi_lrange *ranges;
|
||||
} cb_pcidda_board;
|
||||
static const cb_pcidda_board cb_pcidda_boards[] = {
|
||||
{
|
||||
name: "pci-dda02/12",
|
||||
status: 1,
|
||||
device_id:0x20,
|
||||
ao_chans:2,
|
||||
ao_bits: 12,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
{
|
||||
name: "pci-dda04/12",
|
||||
status: 1,
|
||||
device_id:0x21,
|
||||
ao_chans:4,
|
||||
ao_bits: 12,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
{
|
||||
name: "pci-dda08/12",
|
||||
status: 0,
|
||||
device_id:0x22,
|
||||
ao_chans:8,
|
||||
ao_bits: 12,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
{
|
||||
name: "pci-dda02/16",
|
||||
status: 2,
|
||||
device_id:0x23,
|
||||
ao_chans:2,
|
||||
ao_bits: 16,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
{
|
||||
name: "pci-dda04/16",
|
||||
status: 2,
|
||||
device_id:0x24,
|
||||
ao_chans:4,
|
||||
ao_bits: 16,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
{
|
||||
name: "pci-dda08/16",
|
||||
status: 0,
|
||||
device_id:0x25,
|
||||
ao_chans:8,
|
||||
ao_bits: 16,
|
||||
ranges: &cb_pcidda_ranges,
|
||||
},
|
||||
};
|
||||
|
||||
static DEFINE_PCI_DEVICE_TABLE(cb_pcidda_pci_table) = {
|
||||
{PCI_VENDOR_ID_CB, 0x0020, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{PCI_VENDOR_ID_CB, 0x0021, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{PCI_VENDOR_ID_CB, 0x0022, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{PCI_VENDOR_ID_CB, 0x0023, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{PCI_VENDOR_ID_CB, 0x0024, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{PCI_VENDOR_ID_CB, 0x0025, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
||||
{0}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, cb_pcidda_pci_table);
|
||||
|
||||
/*
|
||||
* Useful for shorthand access to the particular board structure
|
||||
*/
|
||||
#define thisboard ((const cb_pcidda_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;
|
||||
|
||||
unsigned long digitalio;
|
||||
unsigned long dac;
|
||||
//unsigned long control_status;
|
||||
//unsigned long adc_fifo;
|
||||
unsigned int dac_cal1_bits; // bits last written to da calibration register 1
|
||||
unsigned int ao_range[MAX_AO_CHANNELS]; // current range settings for output channels
|
||||
u16 eeprom_data[EEPROM_SIZE]; // software copy of board's eeprom
|
||||
} cb_pcidda_private;
|
||||
|
||||
/*
|
||||
* most drivers define the following macro to make it easy to
|
||||
* access the private structure.
|
||||
*/
|
||||
#define devpriv ((cb_pcidda_private *)dev->private)
|
||||
|
||||
static int cb_pcidda_attach(comedi_device * dev, comedi_devconfig * it);
|
||||
static int cb_pcidda_detach(comedi_device * dev);
|
||||
//static int cb_pcidda_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data);
|
||||
static int cb_pcidda_ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
//static int cb_pcidda_ai_cmd(comedi_device *dev,comedi_subdevice *s);
|
||||
//static int cb_pcidda_ai_cmdtest(comedi_device *dev,comedi_subdevice *s, comedi_cmd *cmd);
|
||||
//static int cb_pcidda_ns_to_timer(unsigned int *ns,int round);
|
||||
static unsigned int cb_pcidda_serial_in(comedi_device * dev);
|
||||
static void cb_pcidda_serial_out(comedi_device * dev, unsigned int value,
|
||||
unsigned int num_bits);
|
||||
static unsigned int cb_pcidda_read_eeprom(comedi_device * dev,
|
||||
unsigned int address);
|
||||
static void cb_pcidda_calibrate(comedi_device * dev, unsigned int channel,
|
||||
unsigned int range);
|
||||
|
||||
/*
|
||||
* 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 comedi_driver driver_cb_pcidda = {
|
||||
driver_name:"cb_pcidda",
|
||||
module:THIS_MODULE,
|
||||
attach:cb_pcidda_attach,
|
||||
detach:cb_pcidda_detach,
|
||||
};
|
||||
|
||||
/*
|
||||
* Attach is called by the Comedi core to configure the driver
|
||||
* for a particular board.
|
||||
*/
|
||||
static int cb_pcidda_attach(comedi_device * dev, comedi_devconfig * it)
|
||||
{
|
||||
comedi_subdevice *s;
|
||||
struct pci_dev *pcidev;
|
||||
int index;
|
||||
|
||||
printk("comedi%d: cb_pcidda: ", dev->minor);
|
||||
|
||||
/*
|
||||
* Allocate the private structure area.
|
||||
*/
|
||||
if (alloc_private(dev, sizeof(cb_pcidda_private)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Probe the device to determine what device in the series it is.
|
||||
*/
|
||||
printk("\n");
|
||||
|
||||
for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
|
||||
pcidev != NULL;
|
||||
pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) {
|
||||
if (pcidev->vendor == PCI_VENDOR_ID_CB) {
|
||||
if (it->options[0] || it->options[1]) {
|
||||
if (pcidev->bus->number != it->options[0] ||
|
||||
PCI_SLOT(pcidev->devfn) !=
|
||||
it->options[1]) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (index = 0; index < N_BOARDS; index++) {
|
||||
if (cb_pcidda_boards[index].device_id ==
|
||||
pcidev->device) {
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!pcidev) {
|
||||
printk("Not a ComputerBoards/MeasurementComputing card on requested position\n");
|
||||
return -EIO;
|
||||
}
|
||||
found:
|
||||
devpriv->pci_dev = pcidev;
|
||||
dev->board_ptr = cb_pcidda_boards + index;
|
||||
// "thisboard" macro can be used from here.
|
||||
printk("Found %s at requested position\n", thisboard->name);
|
||||
|
||||
/*
|
||||
* Enable PCI device and request regions.
|
||||
*/
|
||||
if (comedi_pci_enable(pcidev, thisboard->name)) {
|
||||
printk("cb_pcidda: failed to enable PCI device and request regions\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate the I/O ports.
|
||||
*/
|
||||
devpriv->digitalio =
|
||||
pci_resource_start(devpriv->pci_dev, DIGITALIO_BADRINDEX);
|
||||
devpriv->dac = pci_resource_start(devpriv->pci_dev, DAC_BADRINDEX);
|
||||
|
||||
/*
|
||||
* Warn about the status of the driver.
|
||||
*/
|
||||
if (thisboard->status == 2)
|
||||
printk("WARNING: DRIVER FOR THIS BOARD NOT CHECKED WITH MANUAL. " "WORKS ASSUMING FULL COMPATIBILITY WITH PCI-DDA08/12. " "PLEASE REPORT USAGE TO <ivanmr@altavista.com>.\n");
|
||||
|
||||
/*
|
||||
* Initialize dev->board_name.
|
||||
*/
|
||||
dev->board_name = thisboard->name;
|
||||
|
||||
/*
|
||||
* Allocate the subdevice structures.
|
||||
*/
|
||||
if (alloc_subdevices(dev, 3) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
s = dev->subdevices + 0;
|
||||
/* analog output subdevice */
|
||||
s->type = COMEDI_SUBD_AO;
|
||||
s->subdev_flags = SDF_WRITABLE;
|
||||
s->n_chan = thisboard->ao_chans;
|
||||
s->maxdata = (1 << thisboard->ao_bits) - 1;
|
||||
s->range_table = thisboard->ranges;
|
||||
s->insn_write = cb_pcidda_ao_winsn;
|
||||
// s->subdev_flags |= SDF_CMD_READ;
|
||||
// s->do_cmd = cb_pcidda_ai_cmd;
|
||||
// s->do_cmdtest = cb_pcidda_ai_cmdtest;
|
||||
|
||||
// two 8255 digital io subdevices
|
||||
s = dev->subdevices + 1;
|
||||
subdev_8255_init(dev, s, NULL, devpriv->digitalio);
|
||||
s = dev->subdevices + 2;
|
||||
subdev_8255_init(dev, s, NULL, devpriv->digitalio + PORT2A);
|
||||
|
||||
printk(" eeprom:");
|
||||
for (index = 0; index < EEPROM_SIZE; index++) {
|
||||
devpriv->eeprom_data[index] = cb_pcidda_read_eeprom(dev, index);
|
||||
printk(" %i:0x%x ", index, devpriv->eeprom_data[index]);
|
||||
}
|
||||
printk("\n");
|
||||
|
||||
// set calibrations dacs
|
||||
for (index = 0; index < thisboard->ao_chans; index++)
|
||||
cb_pcidda_calibrate(dev, index, devpriv->ao_range[index]);
|
||||
|
||||
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 cb_pcidda_detach(comedi_device * dev)
|
||||
{
|
||||
/*
|
||||
* Deallocate the I/O ports.
|
||||
*/
|
||||
if (devpriv) {
|
||||
if (devpriv->pci_dev) {
|
||||
if (devpriv->dac) {
|
||||
comedi_pci_disable(devpriv->pci_dev);
|
||||
}
|
||||
pci_dev_put(devpriv->pci_dev);
|
||||
}
|
||||
}
|
||||
// cleanup 8255
|
||||
if (dev->subdevices) {
|
||||
subdev_8255_cleanup(dev, dev->subdevices + 1);
|
||||
subdev_8255_cleanup(dev, dev->subdevices + 2);
|
||||
}
|
||||
|
||||
printk("comedi%d: cb_pcidda: remove\n", dev->minor);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* I will program this later... ;-)
|
||||
*/
|
||||
#if 0
|
||||
static int cb_pcidda_ai_cmd(comedi_device * dev, comedi_subdevice * s)
|
||||
{
|
||||
printk("cb_pcidda_ai_cmd\n");
|
||||
printk("subdev: %d\n", cmd->subdev);
|
||||
printk("flags: %d\n", cmd->flags);
|
||||
printk("start_src: %d\n", cmd->start_src);
|
||||
printk("start_arg: %d\n", cmd->start_arg);
|
||||
printk("scan_begin_src: %d\n", cmd->scan_begin_src);
|
||||
printk("convert_src: %d\n", cmd->convert_src);
|
||||
printk("convert_arg: %d\n", cmd->convert_arg);
|
||||
printk("scan_end_src: %d\n", cmd->scan_end_src);
|
||||
printk("scan_end_arg: %d\n", cmd->scan_end_arg);
|
||||
printk("stop_src: %d\n", cmd->stop_src);
|
||||
printk("stop_arg: %d\n", cmd->stop_arg);
|
||||
printk("chanlist_len: %d\n", cmd->chanlist_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static int cb_pcidda_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_TIMER && cmd->stop_src != TRIG_EXT)
|
||||
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;
|
||||
cb_pcidda_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;
|
||||
cb_pcidda_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;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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. */
|
||||
#if 0
|
||||
static int cb_pcidda_ns_to_timer(unsigned int *ns, int round)
|
||||
{
|
||||
/* trivial timer */
|
||||
return *ns;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int cb_pcidda_ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
unsigned int command;
|
||||
unsigned int channel, range;
|
||||
|
||||
channel = CR_CHAN(insn->chanspec);
|
||||
range = CR_RANGE(insn->chanspec);
|
||||
|
||||
// adjust calibration dacs if range has changed
|
||||
if (range != devpriv->ao_range[channel])
|
||||
cb_pcidda_calibrate(dev, channel, range);
|
||||
|
||||
/* output channel configuration */
|
||||
command = NOSU | ENABLEDAC;
|
||||
|
||||
/* output channel range */
|
||||
switch (range) {
|
||||
case 0:
|
||||
command |= BIP | RANGE10V;
|
||||
break;
|
||||
case 1:
|
||||
command |= BIP | RANGE5V;
|
||||
break;
|
||||
case 2:
|
||||
command |= BIP | RANGE2V5;
|
||||
break;
|
||||
case 3:
|
||||
command |= UNIP | RANGE10V;
|
||||
break;
|
||||
case 4:
|
||||
command |= UNIP | RANGE5V;
|
||||
break;
|
||||
case 5:
|
||||
command |= UNIP | RANGE2V5;
|
||||
break;
|
||||
};
|
||||
|
||||
/* output channel specification */
|
||||
command |= channel << 2;
|
||||
outw(command, devpriv->dac + DACONTROL);
|
||||
|
||||
/* write data */
|
||||
outw(data[0], devpriv->dac + DADATA + channel * 2);
|
||||
|
||||
/* return the number of samples read/written */
|
||||
return 1;
|
||||
}
|
||||
|
||||
// lowlevel read from eeprom
|
||||
static unsigned int cb_pcidda_serial_in(comedi_device * dev)
|
||||
{
|
||||
unsigned int value = 0;
|
||||
int i;
|
||||
const int value_width = 16; // number of bits wide values are
|
||||
|
||||
for (i = 1; i <= value_width; i++) {
|
||||
// read bits most significant bit first
|
||||
if (inw_p(devpriv->dac + DACALIBRATION1) & SERIAL_OUT_BIT) {
|
||||
value |= 1 << (value_width - i);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// lowlevel write to eeprom/dac
|
||||
static void cb_pcidda_serial_out(comedi_device * dev, unsigned int value,
|
||||
unsigned int num_bits)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 1; i <= num_bits; i++) {
|
||||
// send bits most significant bit first
|
||||
if (value & (1 << (num_bits - i)))
|
||||
devpriv->dac_cal1_bits |= SERIAL_IN_BIT;
|
||||
else
|
||||
devpriv->dac_cal1_bits &= ~SERIAL_IN_BIT;
|
||||
outw_p(devpriv->dac_cal1_bits, devpriv->dac + DACALIBRATION1);
|
||||
}
|
||||
}
|
||||
|
||||
// reads a 16 bit value from board's eeprom
|
||||
static unsigned int cb_pcidda_read_eeprom(comedi_device * dev,
|
||||
unsigned int address)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int cal2_bits;
|
||||
unsigned int value;
|
||||
const int max_num_caldacs = 4; // one caldac for every two dac channels
|
||||
const int read_instruction = 0x6; // bits to send to tell eeprom we want to read
|
||||
const int instruction_length = 3;
|
||||
const int address_length = 8;
|
||||
|
||||
// send serial output stream to eeprom
|
||||
cal2_bits = SELECT_EEPROM_BIT | DESELECT_REF_DAC_BIT | DUMMY_BIT;
|
||||
// deactivate caldacs (one caldac for every two channels)
|
||||
for (i = 0; i < max_num_caldacs; i++) {
|
||||
cal2_bits |= DESELECT_CALDAC_BIT(i);
|
||||
}
|
||||
outw_p(cal2_bits, devpriv->dac + DACALIBRATION2);
|
||||
|
||||
// tell eeprom we want to read
|
||||
cb_pcidda_serial_out(dev, read_instruction, instruction_length);
|
||||
// send address we want to read from
|
||||
cb_pcidda_serial_out(dev, address, address_length);
|
||||
|
||||
value = cb_pcidda_serial_in(dev);
|
||||
|
||||
// deactivate eeprom
|
||||
cal2_bits &= ~SELECT_EEPROM_BIT;
|
||||
outw_p(cal2_bits, devpriv->dac + DACALIBRATION2);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// writes to 8 bit calibration dacs
|
||||
static void cb_pcidda_write_caldac(comedi_device * dev, unsigned int caldac,
|
||||
unsigned int channel, unsigned int value)
|
||||
{
|
||||
unsigned int cal2_bits;
|
||||
unsigned int i;
|
||||
const int num_channel_bits = 3; // caldacs use 3 bit channel specification
|
||||
const int num_caldac_bits = 8; // 8 bit calibration dacs
|
||||
const int max_num_caldacs = 4; // one caldac for every two dac channels
|
||||
|
||||
/* write 3 bit channel */
|
||||
cb_pcidda_serial_out(dev, channel, num_channel_bits);
|
||||
// write 8 bit caldac value
|
||||
cb_pcidda_serial_out(dev, value, num_caldac_bits);
|
||||
|
||||
// latch stream into appropriate caldac
|
||||
// deselect reference dac
|
||||
cal2_bits = DESELECT_REF_DAC_BIT | DUMMY_BIT;
|
||||
// deactivate caldacs (one caldac for every two channels)
|
||||
for (i = 0; i < max_num_caldacs; i++) {
|
||||
cal2_bits |= DESELECT_CALDAC_BIT(i);
|
||||
}
|
||||
// activate the caldac we want
|
||||
cal2_bits &= ~DESELECT_CALDAC_BIT(caldac);
|
||||
outw_p(cal2_bits, devpriv->dac + DACALIBRATION2);
|
||||
// deactivate caldac
|
||||
cal2_bits |= DESELECT_CALDAC_BIT(caldac);
|
||||
outw_p(cal2_bits, devpriv->dac + DACALIBRATION2);
|
||||
}
|
||||
|
||||
// returns caldac that calibrates given analog out channel
|
||||
static unsigned int caldac_number(unsigned int channel)
|
||||
{
|
||||
return channel / 2;
|
||||
}
|
||||
|
||||
// returns caldac channel that provides fine gain for given ao channel
|
||||
static unsigned int fine_gain_channel(unsigned int ao_channel)
|
||||
{
|
||||
return 4 * (ao_channel % 2);
|
||||
}
|
||||
|
||||
// returns caldac channel that provides coarse gain for given ao channel
|
||||
static unsigned int coarse_gain_channel(unsigned int ao_channel)
|
||||
{
|
||||
return 1 + 4 * (ao_channel % 2);
|
||||
}
|
||||
|
||||
// returns caldac channel that provides coarse offset for given ao channel
|
||||
static unsigned int coarse_offset_channel(unsigned int ao_channel)
|
||||
{
|
||||
return 2 + 4 * (ao_channel % 2);
|
||||
}
|
||||
|
||||
// returns caldac channel that provides fine offset for given ao channel
|
||||
static unsigned int fine_offset_channel(unsigned int ao_channel)
|
||||
{
|
||||
return 3 + 4 * (ao_channel % 2);
|
||||
}
|
||||
|
||||
// returns eeprom address that provides offset for given ao channel and range
|
||||
static unsigned int offset_eeprom_address(unsigned int ao_channel,
|
||||
unsigned int range)
|
||||
{
|
||||
return 0x7 + 2 * range + 12 * ao_channel;
|
||||
}
|
||||
|
||||
// returns eeprom address that provides gain calibration for given ao channel and range
|
||||
static unsigned int gain_eeprom_address(unsigned int ao_channel,
|
||||
unsigned int range)
|
||||
{
|
||||
return 0x8 + 2 * range + 12 * ao_channel;
|
||||
}
|
||||
|
||||
// returns upper byte of eeprom entry, which gives the coarse adjustment values
|
||||
static unsigned int eeprom_coarse_byte(unsigned int word)
|
||||
{
|
||||
return (word >> 8) & 0xff;
|
||||
}
|
||||
|
||||
// returns lower byte of eeprom entry, which gives the fine adjustment values
|
||||
static unsigned int eeprom_fine_byte(unsigned int word)
|
||||
{
|
||||
return word & 0xff;
|
||||
}
|
||||
|
||||
// set caldacs to eeprom values for given channel and range
|
||||
static void cb_pcidda_calibrate(comedi_device * dev, unsigned int channel,
|
||||
unsigned int range)
|
||||
{
|
||||
unsigned int coarse_offset, fine_offset, coarse_gain, fine_gain;
|
||||
|
||||
// remember range so we can tell when we need to readjust calibration
|
||||
devpriv->ao_range[channel] = range;
|
||||
|
||||
// get values from eeprom data
|
||||
coarse_offset =
|
||||
eeprom_coarse_byte(devpriv->
|
||||
eeprom_data[offset_eeprom_address(channel, range)]);
|
||||
fine_offset =
|
||||
eeprom_fine_byte(devpriv->
|
||||
eeprom_data[offset_eeprom_address(channel, range)]);
|
||||
coarse_gain =
|
||||
eeprom_coarse_byte(devpriv->
|
||||
eeprom_data[gain_eeprom_address(channel, range)]);
|
||||
fine_gain =
|
||||
eeprom_fine_byte(devpriv->
|
||||
eeprom_data[gain_eeprom_address(channel, range)]);
|
||||
|
||||
// set caldacs
|
||||
cb_pcidda_write_caldac(dev, caldac_number(channel),
|
||||
coarse_offset_channel(channel), coarse_offset);
|
||||
cb_pcidda_write_caldac(dev, caldac_number(channel),
|
||||
fine_offset_channel(channel), fine_offset);
|
||||
cb_pcidda_write_caldac(dev, caldac_number(channel),
|
||||
coarse_gain_channel(channel), coarse_gain);
|
||||
cb_pcidda_write_caldac(dev, caldac_number(channel),
|
||||
fine_gain_channel(channel), fine_gain);
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenient macro that defines init_module() and cleanup_module(),
|
||||
* as necessary.
|
||||
*/
|
||||
COMEDI_PCI_INITCLEANUP(driver_cb_pcidda, cb_pcidda_pci_table);
|
Загрузка…
Ссылка в новой задаче