mirror of
https://github.com/torvalds/linux.git
synced 2026-06-02 03:24:19 +02:00
Staging: comedi: add s526 driver
For Sensoray 526 devices From: Everett Wang <everett.wang@everteq.com> 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
4da6a1d8f5
commit
0c988d008c
975
drivers/staging/comedi/drivers/s526.c
Normal file
975
drivers/staging/comedi/drivers/s526.c
Normal file
|
|
@ -0,0 +1,975 @@
|
|||
/*
|
||||
comedi/drivers/s526.c
|
||||
Sensoray s526 Comedi driver
|
||||
|
||||
COMEDI - Linux Control and Measurement Device Interface
|
||||
Copyright (C) 2000 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: s526
|
||||
Description: Sensoray 526 driver
|
||||
Devices: [Sensoray] 526 (s526)
|
||||
Author: Richie
|
||||
Everett Wang <everett.wang@everteq.com>
|
||||
Updated: Thu, 14 Sep. 2006
|
||||
Status: experimental
|
||||
|
||||
Encoder works
|
||||
Analog input works
|
||||
Analog output works
|
||||
PWM output works
|
||||
Commands are not supported yet.
|
||||
|
||||
Configuration Options:
|
||||
|
||||
comedi_config /dev/comedi0 s526 0x2C0,0x3
|
||||
|
||||
*/
|
||||
|
||||
#include "../comedidev.h"
|
||||
#include <linux/ioport.h>
|
||||
|
||||
#define S526_SIZE 64
|
||||
|
||||
#define S526_START_AI_CONV 0
|
||||
#define S526_AI_READ 0
|
||||
|
||||
/* Ports */
|
||||
#define S526_IOSIZE 0x40
|
||||
#define S526_NUM_PORTS 27
|
||||
|
||||
/* registers */
|
||||
#define REG_TCR 0x00
|
||||
#define REG_WDC 0x02
|
||||
#define REG_DAC 0x04
|
||||
#define REG_ADC 0x06
|
||||
#define REG_ADD 0x08
|
||||
#define REG_DIO 0x0A
|
||||
#define REG_IER 0x0C
|
||||
#define REG_ISR 0x0E
|
||||
#define REG_MSC 0x10
|
||||
#define REG_C0L 0x12
|
||||
#define REG_C0H 0x14
|
||||
#define REG_C0M 0x16
|
||||
#define REG_C0C 0x18
|
||||
#define REG_C1L 0x1A
|
||||
#define REG_C1H 0x1C
|
||||
#define REG_C1M 0x1E
|
||||
#define REG_C1C 0x20
|
||||
#define REG_C2L 0x22
|
||||
#define REG_C2H 0x24
|
||||
#define REG_C2M 0x26
|
||||
#define REG_C2C 0x28
|
||||
#define REG_C3L 0x2A
|
||||
#define REG_C3H 0x2C
|
||||
#define REG_C3M 0x2E
|
||||
#define REG_C3C 0x30
|
||||
#define REG_EED 0x32
|
||||
#define REG_EEC 0x34
|
||||
|
||||
static const int s526_ports[] = {
|
||||
REG_TCR,
|
||||
REG_WDC,
|
||||
REG_DAC,
|
||||
REG_ADC,
|
||||
REG_ADD,
|
||||
REG_DIO,
|
||||
REG_IER,
|
||||
REG_ISR,
|
||||
REG_MSC,
|
||||
REG_C0L,
|
||||
REG_C0H,
|
||||
REG_C0M,
|
||||
REG_C0C,
|
||||
REG_C1L,
|
||||
REG_C1H,
|
||||
REG_C1M,
|
||||
REG_C1C,
|
||||
REG_C2L,
|
||||
REG_C2H,
|
||||
REG_C2M,
|
||||
REG_C2C,
|
||||
REG_C3L,
|
||||
REG_C3H,
|
||||
REG_C3M,
|
||||
REG_C3C,
|
||||
REG_EED,
|
||||
REG_EEC
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
unsigned short coutSource:1;
|
||||
unsigned short coutPolarity:1;
|
||||
unsigned short autoLoadResetRcap:3;
|
||||
unsigned short hwCtEnableSource:2;
|
||||
unsigned short ctEnableCtrl:2;
|
||||
unsigned short clockSource:2;
|
||||
unsigned short countDir:1;
|
||||
unsigned short countDirCtrl:1;
|
||||
unsigned short outputRegLatchCtrl:1;
|
||||
unsigned short preloadRegSel:1;
|
||||
unsigned short reserved:1;
|
||||
} counter_mode_register_t;
|
||||
|
||||
union {
|
||||
counter_mode_register_t reg;
|
||||
unsigned short value;
|
||||
} cmReg;
|
||||
|
||||
#define MAX_GPCT_CONFIG_DATA 6
|
||||
|
||||
/* Different Application Classes for GPCT Subdevices */
|
||||
/* The list is not exhaustive and needs discussion! */
|
||||
typedef enum {
|
||||
CountingAndTimeMeasurement,
|
||||
SinglePulseGeneration,
|
||||
PulseTrainGeneration,
|
||||
PositionMeasurement,
|
||||
Miscellaneous
|
||||
} S526_GPCT_APP_CLASS;
|
||||
|
||||
/* Config struct for different GPCT subdevice Application Classes and
|
||||
their options
|
||||
*/
|
||||
typedef struct s526GPCTConfig {
|
||||
S526_GPCT_APP_CLASS app;
|
||||
int data[MAX_GPCT_CONFIG_DATA];
|
||||
} s526_gpct_config_t;
|
||||
|
||||
/*
|
||||
* 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 s526_board_struct {
|
||||
const char *name;
|
||||
int gpct_chans;
|
||||
int gpct_bits;
|
||||
int ad_chans;
|
||||
int ad_bits;
|
||||
int da_chans;
|
||||
int da_bits;
|
||||
int have_dio;
|
||||
} s526_board;
|
||||
|
||||
static const s526_board s526_boards[] = {
|
||||
{
|
||||
name: "s526",
|
||||
gpct_chans:4,
|
||||
gpct_bits:24,
|
||||
ad_chans:8,
|
||||
ad_bits: 16,
|
||||
da_chans:4,
|
||||
da_bits: 16,
|
||||
have_dio:1,
|
||||
}
|
||||
};
|
||||
|
||||
#define ADDR_REG(reg) (dev->iobase + (reg))
|
||||
#define ADDR_CHAN_REG(reg, chan) (dev->iobase + (reg) + (chan) * 8)
|
||||
|
||||
/*
|
||||
* Useful for shorthand access to the particular board structure
|
||||
*/
|
||||
#define thisboard ((const s526_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];
|
||||
|
||||
s526_gpct_config_t s526_gpct_config[4];
|
||||
unsigned short s526_ai_config;
|
||||
} s526_private;
|
||||
/*
|
||||
* most drivers define the following macro to make it easy to
|
||||
* access the private structure.
|
||||
*/
|
||||
#define devpriv ((s526_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 s526_attach(comedi_device * dev, comedi_devconfig * it);
|
||||
static int s526_detach(comedi_device * dev);
|
||||
static comedi_driver driver_s526 = {
|
||||
driver_name:"s526",
|
||||
module:THIS_MODULE,
|
||||
attach:s526_attach,
|
||||
detach:s526_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 s526_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:&s526_boards[0].name,
|
||||
offset:sizeof(s526_board),
|
||||
num_names:sizeof(s526_boards) / sizeof(s526_board),
|
||||
};
|
||||
|
||||
static int s526_gpct_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_gpct_insn_config(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_gpct_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_ai_insn_config(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_ao_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_dio_insn_bits(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data);
|
||||
static int s526_dio_insn_config(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 s526_attach(comedi_device * dev, comedi_devconfig * it)
|
||||
{
|
||||
comedi_subdevice *s;
|
||||
int iobase;
|
||||
int i, n;
|
||||
// sampl_t value;
|
||||
// int subdev_channel = 0;
|
||||
|
||||
printk("comedi%d: s526: ", dev->minor);
|
||||
|
||||
iobase = it->options[0];
|
||||
if (!iobase || !request_region(iobase, S526_IOSIZE, thisboard->name)) {
|
||||
comedi_error(dev, "I/O port conflict");
|
||||
return -EIO;
|
||||
}
|
||||
dev->iobase = iobase;
|
||||
|
||||
printk("iobase=0x%lx\n", dev->iobase);
|
||||
|
||||
/*** make it a little quieter, exw, 8/29/06
|
||||
for (i = 0; i < S526_NUM_PORTS; i++) {
|
||||
printk("0x%02x: 0x%04x\n", ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i])));
|
||||
}
|
||||
***/
|
||||
|
||||
/*
|
||||
* Initialize dev->board_name. Note that we can use the "thisboard"
|
||||
* macro now, since we just initialized it in the last line.
|
||||
*/
|
||||
dev->board_ptr = &s526_boards[0];
|
||||
|
||||
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(s526_private)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* Allocate the subdevice structures. alloc_subdevice() is a
|
||||
* convenient macro defined in comedidev.h.
|
||||
*/
|
||||
dev->n_subdevices = 4;
|
||||
if (alloc_subdevices(dev, dev->n_subdevices) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
s = dev->subdevices + 0;
|
||||
/* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
|
||||
s->type = COMEDI_SUBD_COUNTER;
|
||||
s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
|
||||
/* KG: What does SDF_LSAMPL (see multiq3.c) mean? */
|
||||
s->n_chan = thisboard->gpct_chans;
|
||||
s->maxdata = 0x00ffffff; /* 24 bit counter */
|
||||
s->insn_read = s526_gpct_rinsn;
|
||||
s->insn_config = s526_gpct_insn_config;
|
||||
s->insn_write = s526_gpct_winsn;
|
||||
|
||||
/* Command are not implemented yet, however they are necessary to
|
||||
allocate the necessary memory for the comedi_async struct (used
|
||||
to trigger the GPCT in case of pulsegenerator function */
|
||||
//s->do_cmd = s526_gpct_cmd;
|
||||
//s->do_cmdtest = s526_gpct_cmdtest;
|
||||
//s->cancel = s526_gpct_cancel;
|
||||
|
||||
s = dev->subdevices + 1;
|
||||
//dev->read_subdev=s;
|
||||
/* analog input subdevice */
|
||||
s->type = COMEDI_SUBD_AI;
|
||||
/* we support differential */
|
||||
s->subdev_flags = SDF_READABLE | SDF_DIFF;
|
||||
/* channels 0 to 7 are the regular differential inputs */
|
||||
/* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
|
||||
s->n_chan = 10;
|
||||
s->maxdata = 0xffff;
|
||||
s->range_table = &range_bipolar10;
|
||||
s->len_chanlist = 16; /* This is the maximum chanlist length that
|
||||
the board can handle */
|
||||
s->insn_read = s526_ai_rinsn;
|
||||
s->insn_config = s526_ai_insn_config;
|
||||
|
||||
s = dev->subdevices + 2;
|
||||
/* analog output subdevice */
|
||||
s->type = COMEDI_SUBD_AO;
|
||||
s->subdev_flags = SDF_WRITABLE;
|
||||
s->n_chan = 4;
|
||||
s->maxdata = 0xffff;
|
||||
s->range_table = &range_bipolar10;
|
||||
s->insn_write = s526_ao_winsn;
|
||||
s->insn_read = s526_ao_rinsn;
|
||||
|
||||
s = dev->subdevices + 3;
|
||||
/* digital i/o subdevice */
|
||||
if (thisboard->have_dio) {
|
||||
s->type = COMEDI_SUBD_DIO;
|
||||
s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
|
||||
s->n_chan = 2;
|
||||
s->maxdata = 1;
|
||||
s->range_table = &range_digital;
|
||||
s->insn_bits = s526_dio_insn_bits;
|
||||
s->insn_config = s526_dio_insn_config;
|
||||
} else {
|
||||
s->type = COMEDI_SUBD_UNUSED;
|
||||
}
|
||||
|
||||
printk("attached\n");
|
||||
|
||||
return 1;
|
||||
|
||||
#if 0
|
||||
// Example of Counter Application
|
||||
//One-shot (software trigger)
|
||||
cmReg.reg.coutSource = 0; // out RCAP
|
||||
cmReg.reg.coutPolarity = 1; // Polarity inverted
|
||||
cmReg.reg.autoLoadResetRcap = 1; // Auto load 0:disabled, 1:enabled
|
||||
cmReg.reg.hwCtEnableSource = 3; // NOT RCAP
|
||||
cmReg.reg.ctEnableCtrl = 2; // Hardware
|
||||
cmReg.reg.clockSource = 2; // Internal
|
||||
cmReg.reg.countDir = 1; // Down
|
||||
cmReg.reg.countDirCtrl = 1; // Software
|
||||
cmReg.reg.outputRegLatchCtrl = 0; // latch on read
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
cmReg.reg.reserved = 0;
|
||||
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter
|
||||
outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0
|
||||
|
||||
outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset RCAP (fires one-shot)
|
||||
|
||||
#else
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.reg.coutSource = 0; // out RCAP
|
||||
cmReg.reg.coutPolarity = 0; // Polarity inverted
|
||||
cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled
|
||||
cmReg.reg.hwCtEnableSource = 2; // NOT RCAP
|
||||
cmReg.reg.ctEnableCtrl = 1; // 1: Software, >1 : Hardware
|
||||
cmReg.reg.clockSource = 3; // x4
|
||||
cmReg.reg.countDir = 0; // up
|
||||
cmReg.reg.countDirCtrl = 0; // quadrature
|
||||
cmReg.reg.outputRegLatchCtrl = 0; // latch on read
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
cmReg.reg.reserved = 0;
|
||||
|
||||
n = 0;
|
||||
printk("Mode reg=0x%04x, 0x%04lx\n", cmReg.value, ADDR_CHAN_REG(REG_C0M,
|
||||
n));
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n));
|
||||
udelay(1000);
|
||||
printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n)));
|
||||
|
||||
// Load the pre-laod register high word
|
||||
// value = (sampl_t) (0x55);
|
||||
// outw(value, ADDR_CHAN_REG(REG_C0H, n));
|
||||
|
||||
// Load the pre-laod register low word
|
||||
// value = (sampl_t)(0xaa55);
|
||||
// outw(value, ADDR_CHAN_REG(REG_C0L, n));
|
||||
|
||||
// Write the Counter Control Register
|
||||
// outw(value, ADDR_CHAN_REG(REG_C0C, 0));
|
||||
|
||||
// Reset the counter if it is software preload
|
||||
if (cmReg.reg.autoLoadResetRcap == 0) {
|
||||
outw(0x8000, ADDR_CHAN_REG(REG_C0C, n)); // Reset the counter
|
||||
outw(0x4000, ADDR_CHAN_REG(REG_C0C, n)); // Load the counter from PR0
|
||||
}
|
||||
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, n));
|
||||
udelay(1000);
|
||||
printk("Read back mode reg=0x%04x\n", inw(ADDR_CHAN_REG(REG_C0M, n)));
|
||||
|
||||
#endif
|
||||
printk("Current registres:\n");
|
||||
|
||||
for (i = 0; i < S526_NUM_PORTS; i++) {
|
||||
printk("0x%02lx: 0x%04x\n", ADDR_REG(s526_ports[i]),
|
||||
inw(ADDR_REG(s526_ports[i])));
|
||||
}
|
||||
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 s526_detach(comedi_device * dev)
|
||||
{
|
||||
printk("comedi%d: s526: remove\n", dev->minor);
|
||||
|
||||
if (dev->iobase > 0)
|
||||
release_region(dev->iobase, S526_IOSIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s526_gpct_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int i; // counts the Data
|
||||
int counter_channel = CR_CHAN(insn->chanspec);
|
||||
unsigned short datalow;
|
||||
unsigned short datahigh;
|
||||
|
||||
// Check if (n > 0)
|
||||
if (insn->n <= 0) {
|
||||
printk("s526: INSN_READ: n should be > 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
// Read the low word first
|
||||
for (i = 0; i < insn->n; i++) {
|
||||
datalow = inw(ADDR_CHAN_REG(REG_C0L, counter_channel));
|
||||
datahigh = inw(ADDR_CHAN_REG(REG_C0H, counter_channel));
|
||||
data[i] = (int)(datahigh & 0x00FF);
|
||||
data[i] = (data[i] << 16) | (datalow & 0xFFFF);
|
||||
// printk("s526 GPCT[%d]: %x(0x%04x, 0x%04x)\n", counter_channel, data[i], datahigh, datalow);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int s526_gpct_insn_config(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int subdev_channel = CR_CHAN(insn->chanspec); // Unpack chanspec
|
||||
int i;
|
||||
sampl_t value;
|
||||
|
||||
// printk("s526: GPCT_INSN_CONFIG: Configuring Channel %d\n", subdev_channel);
|
||||
|
||||
for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) {
|
||||
devpriv->s526_gpct_config[subdev_channel].data[i] =
|
||||
insn->data[i];
|
||||
// printk("data[%d]=%x\n", i, insn->data[i]);
|
||||
}
|
||||
|
||||
// Check what type of Counter the user requested, data[0] contains
|
||||
// the Application type
|
||||
switch (insn->data[0]) {
|
||||
case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
|
||||
/*
|
||||
data[0]: Application Type
|
||||
data[1]: Counter Mode Register Value
|
||||
data[2]: Pre-load Register Value
|
||||
data[3]: Conter Control Register
|
||||
*/
|
||||
printk("s526: GPCT_INSN_CONFIG: Configuring Encoder\n");
|
||||
devpriv->s526_gpct_config[subdev_channel].app =
|
||||
PositionMeasurement;
|
||||
|
||||
/*
|
||||
// Example of Counter Application
|
||||
//One-shot (software trigger)
|
||||
cmReg.reg.coutSource = 0; // out RCAP
|
||||
cmReg.reg.coutPolarity = 1; // Polarity inverted
|
||||
cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled
|
||||
cmReg.reg.hwCtEnableSource = 3; // NOT RCAP
|
||||
cmReg.reg.ctEnableCtrl = 2; // Hardware
|
||||
cmReg.reg.clockSource = 2; // Internal
|
||||
cmReg.reg.countDir = 1; // Down
|
||||
cmReg.reg.countDirCtrl = 1; // Software
|
||||
cmReg.reg.outputRegLatchCtrl = 0; // latch on read
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
cmReg.reg.reserved = 0;
|
||||
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
outw(0x0001, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
outw(0x3C68, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter
|
||||
outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0
|
||||
|
||||
outw(0x0008, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset RCAP (fires one-shot)
|
||||
|
||||
*/
|
||||
|
||||
#if 1
|
||||
// Set Counter Mode Register
|
||||
cmReg.reg.coutSource = 0; // out RCAP
|
||||
cmReg.reg.coutPolarity = 0; // Polarity inverted
|
||||
cmReg.reg.autoLoadResetRcap = 0; // Auto load disabled
|
||||
cmReg.reg.hwCtEnableSource = 2; // NOT RCAP
|
||||
cmReg.reg.ctEnableCtrl = 1; // 1: Software, >1 : Hardware
|
||||
cmReg.reg.clockSource = 3; // x4
|
||||
cmReg.reg.countDir = 0; // up
|
||||
cmReg.reg.countDirCtrl = 0; // quadrature
|
||||
cmReg.reg.outputRegLatchCtrl = 0; // latch on read
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
cmReg.reg.reserved = 0;
|
||||
|
||||
// Set Counter Mode Register
|
||||
// printk("s526: Counter Mode register=%x\n", cmReg.value);
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Reset the counter if it is software preload
|
||||
if (cmReg.reg.autoLoadResetRcap == 0) {
|
||||
outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter
|
||||
// outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0
|
||||
}
|
||||
#else
|
||||
cmReg.reg.countDirCtrl = 0; // 0 quadrature, 1 software control
|
||||
|
||||
// data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4
|
||||
if (insn->data[1] == GPCT_X2) {
|
||||
cmReg.reg.clockSource = 1;
|
||||
} else if (insn->data[1] == GPCT_X4) {
|
||||
cmReg.reg.clockSource = 2;
|
||||
} else {
|
||||
cmReg.reg.clockSource = 0;
|
||||
}
|
||||
|
||||
// When to take into account the indexpulse:
|
||||
if (insn->data[2] == GPCT_IndexPhaseLowLow) {
|
||||
} else if (insn->data[2] == GPCT_IndexPhaseLowHigh) {
|
||||
} else if (insn->data[2] == GPCT_IndexPhaseHighLow) {
|
||||
} else if (insn->data[2] == GPCT_IndexPhaseHighHigh) {
|
||||
}
|
||||
// Take into account the index pulse?
|
||||
if (insn->data[3] == GPCT_RESET_COUNTER_ON_INDEX)
|
||||
cmReg.reg.autoLoadResetRcap = 4; // Auto load with INDEX^
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.value = (sampl_t) (insn->data[1] & 0xFFFF);
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Load the pre-laod register high word
|
||||
value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
|
||||
// Load the pre-laod register low word
|
||||
value = (sampl_t) (insn->data[2] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
// Write the Counter Control Register
|
||||
if (insn->data[3] != 0) {
|
||||
value = (sampl_t) (insn->data[3] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
|
||||
}
|
||||
// Reset the counter if it is software preload
|
||||
if (cmReg.reg.autoLoadResetRcap == 0) {
|
||||
outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Reset the counter
|
||||
outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); // Load the counter from PR0
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
|
||||
/*
|
||||
data[0]: Application Type
|
||||
data[1]: Counter Mode Register Value
|
||||
data[2]: Pre-load Register 0 Value
|
||||
data[3]: Pre-load Register 1 Value
|
||||
data[4]: Conter Control Register
|
||||
*/
|
||||
printk("s526: GPCT_INSN_CONFIG: Configuring SPG\n");
|
||||
devpriv->s526_gpct_config[subdev_channel].app =
|
||||
SinglePulseGeneration;
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.value = (sampl_t) (insn->data[1] & 0xFFFF);
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 0 high word
|
||||
value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 0 low word
|
||||
value = (sampl_t) (insn->data[2] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.value = (sampl_t) (insn->data[1] & 0xFFFF);
|
||||
cmReg.reg.preloadRegSel = 1; // PR1
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 1 high word
|
||||
value = (sampl_t) ((insn->data[3] >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 1 low word
|
||||
value = (sampl_t) (insn->data[3] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
// Write the Counter Control Register
|
||||
if (insn->data[3] != 0) {
|
||||
value = (sampl_t) (insn->data[3] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
|
||||
}
|
||||
break;
|
||||
|
||||
case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
|
||||
/*
|
||||
data[0]: Application Type
|
||||
data[1]: Counter Mode Register Value
|
||||
data[2]: Pre-load Register 0 Value
|
||||
data[3]: Pre-load Register 1 Value
|
||||
data[4]: Conter Control Register
|
||||
*/
|
||||
printk("s526: GPCT_INSN_CONFIG: Configuring PTG\n");
|
||||
devpriv->s526_gpct_config[subdev_channel].app =
|
||||
PulseTrainGeneration;
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.value = (sampl_t) (insn->data[1] & 0xFFFF);
|
||||
cmReg.reg.preloadRegSel = 0; // PR0
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 0 high word
|
||||
value = (sampl_t) ((insn->data[2] >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 0 low word
|
||||
value = (sampl_t) (insn->data[2] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
// Set Counter Mode Register
|
||||
cmReg.value = (sampl_t) (insn->data[1] & 0xFFFF);
|
||||
cmReg.reg.preloadRegSel = 1; // PR1
|
||||
outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 1 high word
|
||||
value = (sampl_t) ((insn->data[3] >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
|
||||
// Load the pre-laod register 1 low word
|
||||
value = (sampl_t) (insn->data[3] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
|
||||
// Write the Counter Control Register
|
||||
if (insn->data[3] != 0) {
|
||||
value = (sampl_t) (insn->data[3] & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
printk("s526: unsupported GPCT_insn_config\n");
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return insn->n;
|
||||
}
|
||||
|
||||
static int s526_gpct_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int subdev_channel = CR_CHAN(insn->chanspec); // Unpack chanspec
|
||||
sampl_t value;
|
||||
|
||||
printk("s526: GPCT_INSN_WRITE on channel %d\n", subdev_channel);
|
||||
cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel));
|
||||
printk("s526: Counter Mode Register: %x\n", cmReg.value);
|
||||
// Check what Application of Counter this channel is configured for
|
||||
switch (devpriv->s526_gpct_config[subdev_channel].app) {
|
||||
case PositionMeasurement:
|
||||
printk("S526: INSN_WRITE: PM\n");
|
||||
outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H,
|
||||
subdev_channel));
|
||||
outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
break;
|
||||
|
||||
case SinglePulseGeneration:
|
||||
printk("S526: INSN_WRITE: SPG\n");
|
||||
outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H,
|
||||
subdev_channel));
|
||||
outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
break;
|
||||
|
||||
case PulseTrainGeneration:
|
||||
/* data[0] contains the PULSE_WIDTH
|
||||
data[1] contains the PULSE_PERIOD
|
||||
@pre PULSE_PERIOD > PULSE_WIDTH > 0
|
||||
The above periods must be expressed as a multiple of the
|
||||
pulse frequency on the selected source
|
||||
*/
|
||||
printk("S526: INSN_WRITE: PTG\n");
|
||||
if ((insn->data[1] > insn->data[0]) && (insn->data[0] > 0)) {
|
||||
(devpriv->s526_gpct_config[subdev_channel]).data[0] =
|
||||
insn->data[0];
|
||||
(devpriv->s526_gpct_config[subdev_channel]).data[1] =
|
||||
insn->data[1];
|
||||
} else {
|
||||
printk("%d \t %d\n", insn->data[1], insn->data[2]);
|
||||
printk("s526: INSN_WRITE: PTG: Problem with Pulse params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
value = (sampl_t) ((*data >> 16) & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel));
|
||||
value = (sampl_t) (*data & 0xFFFF);
|
||||
outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel));
|
||||
break;
|
||||
default: // Impossible
|
||||
printk("s526: INSN_WRITE: Functionality %d not implemented yet\n", devpriv->s526_gpct_config[subdev_channel].app);
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
// return the number of samples written
|
||||
return insn->n;
|
||||
}
|
||||
|
||||
#define ISR_ADC_DONE 0x4
|
||||
static int s526_ai_insn_config(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
|
||||
if (insn->n < 1)
|
||||
return result;
|
||||
|
||||
result = insn->n;
|
||||
|
||||
/* data[0] : channels was set in relevant bits.
|
||||
data[1] : delay
|
||||
*/
|
||||
/* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
|
||||
* enable channels here. The channel should be enabled in the
|
||||
* INSN_READ handler. */
|
||||
|
||||
// Enable ADC interrupt
|
||||
outw(ISR_ADC_DONE, ADDR_REG(REG_IER));
|
||||
// printk("s526: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC)));
|
||||
devpriv->s526_ai_config = (data[0] & 0x3FF) << 5;
|
||||
if (data[1] > 0)
|
||||
devpriv->s526_ai_config |= 0x8000; //set the delay
|
||||
|
||||
devpriv->s526_ai_config |= 0x0001; // ADC start bit.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* "instructions" read/write data in "one-shot" or "software-triggered"
|
||||
* mode.
|
||||
*/
|
||||
static int s526_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int n, i;
|
||||
int chan = CR_CHAN(insn->chanspec);
|
||||
unsigned short value;
|
||||
unsigned int d;
|
||||
unsigned int status;
|
||||
|
||||
/* Set configured delay, enable channel for this channel only,
|
||||
* select "ADC read" channel, set "ADC start" bit. */
|
||||
value = (devpriv->s526_ai_config & 0x8000) |
|
||||
((1 << 5) << chan) | (chan << 1) | 0x0001;
|
||||
|
||||
/* convert n samples */
|
||||
for (n = 0; n < insn->n; n++) {
|
||||
/* trigger conversion */
|
||||
outw(value, ADDR_REG(REG_ADC));
|
||||
// printk("s526: Wrote 0x%04x to ADC\n", value);
|
||||
// printk("s526: ADC reg=0x%04x\n", inw(ADDR_REG(REG_ADC)));
|
||||
|
||||
#define TIMEOUT 100
|
||||
/* wait for conversion to end */
|
||||
for (i = 0; i < TIMEOUT; i++) {
|
||||
status = inw(ADDR_REG(REG_ISR));
|
||||
if (status & ISR_ADC_DONE) {
|
||||
outw(ISR_ADC_DONE, ADDR_REG(REG_ISR));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == TIMEOUT) {
|
||||
/* rt_printk() should be used instead of printk()
|
||||
* whenever the code can be called from real-time. */
|
||||
rt_printk("s526: ADC(0x%04x) timeout\n",
|
||||
inw(ADDR_REG(REG_ISR)));
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/* read data */
|
||||
d = inw(ADDR_REG(REG_ADD));
|
||||
// printk("AI[%d]=0x%04x\n", n, (unsigned short)(d & 0xFFFF));
|
||||
|
||||
/* munge data */
|
||||
data[n] = d ^ 0x8000;
|
||||
}
|
||||
|
||||
/* return the number of samples read/written */
|
||||
return n;
|
||||
}
|
||||
|
||||
static int s526_ao_winsn(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int i;
|
||||
int chan = CR_CHAN(insn->chanspec);
|
||||
unsigned short val;
|
||||
|
||||
// printk("s526_ao_winsn\n");
|
||||
val = chan << 1;
|
||||
// outw(val, dev->iobase + REG_DAC);
|
||||
outw(val, ADDR_REG(REG_DAC));
|
||||
|
||||
/* 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 + REG_ADD); // write the data to preload register
|
||||
outw(data[i], ADDR_REG(REG_ADD)); // write the data to preload register
|
||||
devpriv->ao_readback[chan] = data[i];
|
||||
// outw(val + 1, dev->iobase + REG_DAC); // starts the D/A conversion.
|
||||
outw(val + 1, ADDR_REG(REG_DAC)); // starts the D/A conversion.
|
||||
}
|
||||
|
||||
/* 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 s526_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 s526_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, ADDR_REG(REG_DIO));
|
||||
}
|
||||
|
||||
/* on return, data[1] contains the value of the digital
|
||||
* input and output lines. */
|
||||
data[1] = inw(ADDR_REG(REG_DIO)) & 0xFF; // low 8 bits are the data
|
||||
/* 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 s526_dio_insn_config(comedi_device * dev, comedi_subdevice * s,
|
||||
comedi_insn * insn, lsampl_t * data)
|
||||
{
|
||||
int chan = CR_CHAN(insn->chanspec);
|
||||
sampl_t value;
|
||||
|
||||
printk("S526 DIO insn_config\n");
|
||||
|
||||
if (insn->n != 1)
|
||||
return -EINVAL;
|
||||
|
||||
value = inw(ADDR_REG(REG_DIO));
|
||||
|
||||
/* 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. */
|
||||
|
||||
if (data[0] == COMEDI_OUTPUT) {
|
||||
value |= 1 << (chan + 10); // bit 10/11 set the group 1/2's mode
|
||||
s->io_bits |= (0xF << chan);
|
||||
} else {
|
||||
value &= ~(1 << (chan + 10)); // 1 is output, 0 is input.
|
||||
s->io_bits &= ~(0xF << chan);
|
||||
}
|
||||
outw(value, ADDR_REG(REG_DIO));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenient macro that defines init_module() and cleanup_module(),
|
||||
* as necessary.
|
||||
*/
|
||||
COMEDI_INITCLEANUP(driver_s526);
|
||||
Loading…
Reference in New Issue
Block a user