Module: xenomai-abe Branch: analogy Commit: c97b8ef6e18ef5befdd70ab363742506183d3bfe URL: http://git.xenomai.org/?p=xenomai-abe.git;a=commit;h=c97b8ef6e18ef5befdd70ab363742506183d3bfe
Author: Simon Boulay <simon.bou...@gmail.com> Date: Sun Jan 3 15:47:29 2010 +0100 analogy: add s526 driver --- ksrc/drivers/analogy/Config.in | 3 +- ksrc/drivers/analogy/Kconfig | 1 + ksrc/drivers/analogy/Makefile | 3 +- ksrc/drivers/analogy/sensoray/Config.in | 2 + ksrc/drivers/analogy/sensoray/Kconfig | 5 + ksrc/drivers/analogy/sensoray/Makefile | 30 + ksrc/drivers/analogy/sensoray/s526.c | 975 +++++++++++++++++++++++++++++++ 7 files changed, 1017 insertions(+), 2 deletions(-) diff --git a/ksrc/drivers/analogy/Config.in b/ksrc/drivers/analogy/Config.in index 4bc1197..0336a85 100644 --- a/ksrc/drivers/analogy/Config.in +++ b/ksrc/drivers/analogy/Config.in @@ -7,10 +7,11 @@ comment 'ANALOGY drivers' dep_tristate 'ANALOGY interface' CONFIG_XENO_DRIVERS_ANALOGY $CONFIG_XENO_SKIN_RTDM -if [ "$CONFIG_XENO_DRIVERS_ANALOGY" != "n" ]; then +if [ "$CONFIG_XENO_DRIVERS_ANALOGY" != "n" ]; then source drivers/xenomai/analogy/testing/Config.in source drivers/xenomai/analogy/intel/Config.in source drivers/xenomai/analogy/national_instruments/Config.in + source drivers/xenomai/analogy/sensoray/Config.in fi endmenu diff --git a/ksrc/drivers/analogy/Kconfig b/ksrc/drivers/analogy/Kconfig index 43d61a7..b33fd67 100644 --- a/ksrc/drivers/analogy/Kconfig +++ b/ksrc/drivers/analogy/Kconfig @@ -44,5 +44,6 @@ config XENO_DRIVERS_ANALOGY_DRIVER_DEBUG_LEVEL source drivers/xenomai/analogy/testing/Kconfig source drivers/xenomai/analogy/intel/Kconfig source drivers/xenomai/analogy/national_instruments/Kconfig +source drivers/xenomai/analogy/sensoray/Kconfig endmenu diff --git a/ksrc/drivers/analogy/Makefile b/ksrc/drivers/analogy/Makefile index c51f5e1..363d625 100644 --- a/ksrc/drivers/analogy/Makefile +++ b/ksrc/drivers/analogy/Makefile @@ -4,7 +4,7 @@ ifeq ($(PATCHLEVEL),6) EXTRA_CFLAGS += -D__IN_XENOMAI__ -Iinclude/xenomai -Idrivers/xenomai/analogy -obj-$(CONFIG_XENO_DRIVERS_ANALOGY) += xeno_analogy.o testing/ intel/ national_instruments/ +obj-$(CONFIG_XENO_DRIVERS_ANALOGY) += xeno_analogy.o testing/ intel/ national_instruments/ sensoray/ xeno_analogy-y := \ buffer.o \ @@ -29,6 +29,7 @@ subdir-$(CONFIG_XENO_DRIVERS_ANALOGY_FAKE) += testing subdir-$(CONFIG_XENO_DRIVERS_ANALOGY_LOOP) += testing subdir-$(CONFIG_XENO_DRIVERS_ANALOGY_8255) += intel subdir-$(CONFIG_XENO_DRIVERS_ANALOGY_NI_MITE) += national_instruments +subdir-$(CONFIG_XENO_DRIVERS_ANALOGY_S526) += sensoray obj-$(CONFIG_XENO_DRIVERS_ANALOGY) += xeno_analogy.o diff --git a/ksrc/drivers/analogy/sensoray/Config.in b/ksrc/drivers/analogy/sensoray/Config.in new file mode 100644 index 0000000..00e956c --- /dev/null +++ b/ksrc/drivers/analogy/sensoray/Config.in @@ -0,0 +1,2 @@ + +dep_tristate 'Sensoray Model 526 driver' CONFIG_XENO_DRIVERS_ANALOGY_S526 $CONFIG_XENO_DRIVERS_ANALOGY diff --git a/ksrc/drivers/analogy/sensoray/Kconfig b/ksrc/drivers/analogy/sensoray/Kconfig new file mode 100644 index 0000000..ce5aa51 --- /dev/null +++ b/ksrc/drivers/analogy/sensoray/Kconfig @@ -0,0 +1,5 @@ + +config XENO_DRIVERS_ANALOGY_S526 + depends on XENO_DRIVERS_ANALOGY + tristate "Sensoray Model 526 driver" + default n diff --git a/ksrc/drivers/analogy/sensoray/Makefile b/ksrc/drivers/analogy/sensoray/Makefile new file mode 100644 index 0000000..773da98 --- /dev/null +++ b/ksrc/drivers/analogy/sensoray/Makefile @@ -0,0 +1,30 @@ +ifeq ($(PATCHLEVEL),6) + +# Makefile flag for Linux v2.6 + +EXTRA_CFLAGS += -D__IN_XENOMAI__ -Iinclude/xenomai + +obj-$(CONFIG_XENO_DRIVERS_ANALOGY_S526) += analogy_s526.o + +analogy_s526-y := s526.o + +else + +# Makefile flag for Linux v2.4 + +O_TARGET := built-in.o + +obj-$(CONFIG_XENO_DRIVERS_ANALOGY_S526) += analogy_s526.o + +analogy_s526-objs := s526.o + +export-objs := $(analogy_s526-objs) + +EXTRA_CFLAGS += -D__IN_XENOMAI__ -I$(TOPDIR)/include/xenomai -I$(TOPDIR)/include/xenomai/compat + +include $(TOPDIR)/Rules.make + +analogy_s526.o: $(analogy_s526-objs) + $(LD) -r -o $@ $(analogy_s526-objs) + +endif diff --git a/ksrc/drivers/analogy/sensoray/s526.c b/ksrc/drivers/analogy/sensoray/s526.c new file mode 100644 index 0000000..ac95653 --- /dev/null +++ b/ksrc/drivers/analogy/sensoray/s526.c @@ -0,0 +1,975 @@ +/** + * @file + * Analogy driver for Sensoray Model 526 board + * + * Copyright (C) 2009 Simon Boulay <simon.bou...@gmail.com> + * + * Derived from comedi: + * Copyright (C) 2000 David A. Schleef <d...@schleef.org> + * 2006 Everett Wang <everett.w...@everteq.com> + * 2009 Ian Abbott <abbo...@mev.co.uk> + * + * This code 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 code 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 Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + * Original code comes from comedi linux-next staging driver (2009.12.20) + * Board documentation: http://www.sensoray.com/products/526data.htm + * Everything should work as in comedi: + * - Encoder works + * - Analog input works + * - Analog output works + * - PWM output works + * - Commands are not supported yet. + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <asm/byteorder.h> +#include <analogy/analogy_driver.h> + +#define BOARD_NAME "Sensoray Model 526" +#define S526_GPCT_CHANS 4 +#define S526_GPCT_BITS 24 +#define S526_AI_CHANS 10 /* 8 regular differential inputs + * channel 8 is "reference 0" (+10V) + * channel 9 is "reference 1" (0V) */ +#define S526_AI_BITS 16 +#define S526_AO_CHANS 4 +#define S526_AO_BITS 16 +#define S526_HAVE_DIO 1 +#define S526_DIO_CHANS 8 +#define S526_DIO_BITS 1 + +#define S526_START_AI_CONV 0 +#define S526_AI_READ 0 + +/* Ports */ +#define S526_IOSIZE 0x40 /* 64 bytes */ +#define S526_DEFAULT_ADDRESS 0x2C0 /* Manufacturing default */ +#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 +}; + +struct counter_mode_register_t { +#if defined (__LITTLE_ENDIAN_BITFIELD) + 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; +#elif defined(__BIG_ENDIAN_BITFIELD) + unsigned short reserved:1; + unsigned short preloadRegSel:1; + unsigned short outputRegLatchCtrl:1; + unsigned short countDirCtrl:1; + unsigned short countDir:1; + unsigned short clockSource:2; + unsigned short ctEnableCtrl:2; + unsigned short hwCtEnableSource:2; + unsigned short autoLoadResetRcap:3; + unsigned short coutPolarity:1; + unsigned short coutSource:1; +#else +#error Unknown bit field order +#endif +}; + +union cmReg { + struct counter_mode_register_t reg; + unsigned short value; +}; + +#define MAX_GPCT_CONFIG_DATA 6 + +/* Different Application Classes for GPCT Subdevices */ +/* The list is not exhaustive and needs discussion! */ +enum S526_GPCT_APP_CLASS { + CountingAndTimeMeasurement, + SinglePulseGeneration, + PulseTrainGeneration, + PositionMeasurement, + Miscellaneous +}; + +/* Config struct for different GPCT subdevice Application Classes and + * their options + */ +struct s526GPCTConfig { + enum S526_GPCT_APP_CLASS app; + int data[MAX_GPCT_CONFIG_DATA]; +}; + +typedef struct s526_priv { + unsigned long io_base; +} s526_priv_t; + +struct s526_subd_gpct_priv { + struct s526GPCTConfig config[4]; +}; + +struct s526_subd_ai_priv { + uint16_t config; +}; + +struct s526_subd_ao_priv { + uint16_t readback[2]; +}; + +struct s526_subd_dio_priv { + int io_bits; + unsigned int state; +}; + +#define devpriv ((s526_priv_t*)(dev->priv)) + +#define ADDR_REG(reg) (devpriv->io_base + (reg)) +#define ADDR_CHAN_REG(reg, chan) (devpriv->io_base + (reg) + (chan) * 8) + +static int s526_gpct_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_gpct_rinsn(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_gpct_winsn(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_ai_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_ai_rinsn(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_ao_winsn(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_ao_rinsn(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_dio_insn_bits(a4l_subd_t *subd, a4l_kinsn_t *insn); +static int s526_dio_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn); + +static int s526_gpct_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_gpct_priv *subdpriv = + (struct s526_subd_gpct_priv *)subd->priv; + unsigned int *data = (unsigned int *)insn->data; + int subdev_channel = CR_CHAN(insn->chan_desc); + int i; + short value; + union cmReg cmReg; + + /* a4l_info(dev, "s526_gpct_insn_config: Configuring Channel %d\n", subdev_channel); */ + + for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) { + /* subdpriv->config[subdev_channel].data[i] = insn->data[i]; */ + subdpriv->config[subdev_channel].data[i] = data[i]; + /* a4l_info(dev, "data[%d]=%x\n", i, insn->data[i]); */ + } + + /* Check what type of Counter the user requested, data[0] contains */ + /* the Application type */ + switch (data[0]) { + case A4L_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 + */ + /* a4l_info(dev, "s526_gpct_insn_config: Configuring Encoder\n"); */ + subdpriv->config[subdev_channel].app = PositionMeasurement; + +#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 = 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) */ + +#endif + +#if 1 + /* Set Counter Mode Register */ + cmReg.value = data[1] & 0xFFFF; + + /* a44_info(dev, "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 (data[1] == GPCT_X2) { + cmReg.reg.clockSource = 1; + } else if (data[1] == GPCT_X4) { + cmReg.reg.clockSource = 2; + } else { + cmReg.reg.clockSource = 0; + } + + /* When to take into account the indexpulse: */ + if (data[2] == GPCT_IndexPhaseLowLow) { + } else if (data[2] == GPCT_IndexPhaseLowHigh) { + } else if (data[2] == GPCT_IndexPhaseHighLow) { + } else if (data[2] == GPCT_IndexPhaseHighHigh) { + } + /* Take into account the index pulse? */ + if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) + cmReg.reg.autoLoadResetRcap = 4; /* Auto load with INDEX^ */ + + /* Set Counter Mode Register */ + cmReg.value = (short)(data[1] & 0xFFFF); + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register high word */ + value = (short)((data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register low word */ + value = (short)(data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (data[3] != 0) { + value = (short)(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 A4L_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 + */ + /* a4l_info(dev, "s526_gpct_insn_config: Configuring SPG\n"); */ + subdpriv->config[subdev_channel].app = SinglePulseGeneration; + + /* Set Counter Mode Register */ + cmReg.value = (short)(data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 0; /* PR0 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 0 high word */ + value = (short)((data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 0 low word */ + value = (short)(data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Set Counter Mode Register */ + cmReg.value = (short)(data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 1; /* PR1 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 1 high word */ + value = (short)((data[3] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 1 low word */ + value = (short)(data[3] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (data[4] != 0) { + value = (short)(data[4] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } + break; + + case A4L_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 + */ + /* a4l_info(dev, "s526_gpct_insn_config: Configuring PTG\n"); */ + subdpriv->config[subdev_channel].app = PulseTrainGeneration; + + /* Set Counter Mode Register */ + cmReg.value = (short)(data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 0; /* PR0 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 0 high word */ + value = (short)((data[2] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 0 low word */ + value = (short)(data[2] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Set Counter Mode Register */ + cmReg.value = (short)(data[1] & 0xFFFF); + cmReg.reg.preloadRegSel = 1; /* PR1 */ + outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); + + /* Load the pre-load register 1 high word */ + value = (short)((data[3] >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + + /* Load the pre-load register 1 low word */ + value = (short)(data[3] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + + /* Write the Counter Control Register */ + if (data[4] != 0) { + value = (short)(data[4] & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); + } + break; + + default: + a4l_err(dev, "s526_gpct_insn_config: unsupported GPCT_insn_config\n"); + return -EINVAL; + break; + } + + return 0; +} + +static int s526_gpct_rinsn(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + uint32_t *data = (uint32_t *)insn->data; + int counter_channel = CR_CHAN(insn->chan_desc); + unsigned short datalow; + unsigned short datahigh; + int i; + + if (insn->data_size <= 0) { + a4l_err(dev, "s526_gpct_rinsn: n should be > 0\n"); + return -EINVAL; + } + + for (i = 0; i < insn->data_size; 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); + /* a4l_info(dev, "s526_gpct_rinsn GPCT[%d]: %x(0x%04x, 0x%04x)\n", counter_channel, data[i], datahigh, datalow); */ + } + + return 0; +} + +static int s526_gpct_winsn(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_gpct_priv *subdpriv = + (struct s526_subd_gpct_priv *)subd->priv; + uint32_t *data = (uint32_t *)insn->data; + int subdev_channel = CR_CHAN(insn->chan_desc); /* Unpack chanspec */ + short value; + union cmReg cmReg; + + + /* a4l_info(dev, "s526_gpct_winsn: GPCT_INSN_WRITE on channel %d\n", subdev_channel); */ + + cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel)); + /* a4l_info(dev, "s526_gpct_winsn: Counter Mode Register: %x\n", cmReg.value); */ + + /* Check what Application of Counter this channel is configured for */ + switch (subdpriv->config[subdev_channel].app) { + case PositionMeasurement: + /* a4l_info(dev, "s526_gpct_winsn: 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: + /* a4l_info(dev, "s526_gpct_winsn: 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 + */ + /* a4l_info(dev, "s526_gpct_winsn: INSN_WRITE: PTG\n"); */ + if ((data[1] > data[0]) && (data[0] > 0)) { + (subdpriv->config[subdev_channel]).data[0] = data[0]; + (subdpriv->config[subdev_channel]).data[1] = data[1]; + } else { + a4l_err(dev, "s526_gpct_winsn: INSN_WRITE: PTG: Problem with Pulse params -> %du %du\n", + data[0], data[1]); + return -EINVAL; + } + + value = (short)((*data >> 16) & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); + value = (short)(*data & 0xFFFF); + outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); + break; + default: /* Impossible */ + a4l_err(dev, "s526_gpct_winsn: INSN_WRITE: Functionality %d not implemented yet\n", + subdpriv->config[subdev_channel].app); + return -EINVAL; + break; + } + + return 0; +} + +#define ISR_ADC_DONE 0x4 +static int s526_ai_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_ai_priv *subdpriv = + (struct s526_subd_ai_priv *)subd->priv; + unsigned int *data = (unsigned int *)insn->data; + + if (insn->data_size < 1) + return -EINVAL; + + /* 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)); + /* a4l_info(dev, "s526_ai_insn_config: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC))); */ + subdpriv->config = (data[0] & 0x3FF) << 5; + if (data[1] > 0) + subdpriv->config |= 0x8000; /* set the delay */ + + subdpriv->config |= 0x0001; /* ADC start bit. */ + + return 0; +} + +static int s526_ai_rinsn(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_ai_priv *subdpriv = + (struct s526_subd_ai_priv *)subd->priv; + uint16_t *data = (uint16_t *)insn->data; + int n, i; + int chan = CR_CHAN(insn->chan_desc); + uint16_t value; + uint16_t d; + uint16_t status; + + /* Set configured delay, enable channel for this channel only, + * select "ADC read" channel, set "ADC start" bit. */ + value = (subdpriv->config & 0x8000) | + ((1 << 5) << chan) | (chan << 1) | 0x0001; + + /* convert n samples */ + for (n = 0; n < insn->data_size; n++) { + /* trigger conversion */ + outw(value, ADDR_REG(REG_ADC)); + /* a4l_info(dev, "s526_ai_rinsn: Wrote 0x%04x to ADC\n", value); */ + /* a4l_info(dev, "s526_ai_rinsn: 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) { + a4l_warn(dev, "s526_ai_rinsn: ADC(0x%04x) timeout\n", inw(ADDR_REG(REG_ISR))); + return -ETIMEDOUT; + } + + /* read data */ + d = inw(ADDR_REG(REG_ADD)); + /* a4l_info(dev "s526_ai_rinsn: AI[%d]=0x%04x\n", n, (unsigned short)(d & 0xFFFF)); */ + + /* munge data */ + data[n] = d ^ 0x8000; + } + + return 0; +} + +static int s526_ao_winsn(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_ao_priv *subdpriv = + (struct s526_subd_ao_priv *)subd->priv; + uint16_t *data = (uint16_t *)insn->data; + int i; + int chan = CR_CHAN(insn->chan_desc); + uint16_t val; + + val = chan << 1; + 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->data_size; i++) { + /* a typical programming sequence */ + outw(data[i], ADDR_REG(REG_ADD)); /* write the data to preload register */ + subdpriv->readback[chan] = data[i]; + outw(val + 1, ADDR_REG(REG_DAC)); /* starts the D/A conversion. */ + } + + return 0; +} + +/* 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(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + struct s526_subd_ao_priv *subdpriv = + (struct s526_subd_ao_priv *)subd->priv; + uint16_t *data = (uint16_t *)insn->data; + int i; + int chan = CR_CHAN(insn->chan_desc); + + for (i = 0; i < insn->data_size; i++) + data[i] = subdpriv->readback[chan]; + + return 0; +} + +static int s526_dio_insn_config(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_dio_priv *subdpriv = + (struct s526_subd_dio_priv *)subd->priv; + unsigned int *data = (unsigned int *)insn->data; + int chan = CR_CHAN(insn->chan_desc); + int group, mask; + + group = chan >> 2; + mask = 0xF << (group << 2); + switch (data[0]) { + case A4L_INSN_CONFIG_DIO_OUTPUT: + subdpriv->state |= 1 << (group + 10); /* bit 10/11 set the group + 1/2's mode */ + subdpriv->io_bits |= mask; + break; + case A4L_INSN_CONFIG_DIO_INPUT: + subdpriv->state &= ~(1 << (group + 10)); /* 1 is output, 0 is + * input. */ + subdpriv->io_bits &= ~mask; + break; + case A4L_INSN_CONFIG_DIO_QUERY: + data[1] = + (subdpriv->io_bits & mask) ? A4L_OUTPUT : A4L_INPUT; + return insn->data_size; + default: + return -EINVAL; + } + outw(subdpriv->state, ADDR_REG(REG_DIO)); + + return 0; +} + +/* 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(a4l_subd_t *subd, a4l_kinsn_t *insn) +{ + a4l_dev_t *dev = subd->dev; + struct s526_subd_dio_priv *subdpriv = + (struct s526_subd_dio_priv *)subd->priv; + uint8_t *data = (uint8_t *)insn->data; + + if (insn->data_size != 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]) { + subdpriv->state &= ~(data[0]); + subdpriv->state |= data[0] & data[1]; + /* Write out the new digital output lines */ + outw(subdpriv->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 */ + /* insn->data[1]=subdpriv->state & 0xFF; */ + + return 0; +} + +/* --- Channels descriptor --- */ + +static a4l_chdesc_t s526_chan_desc_gpct = { + .mode = A4L_CHAN_GLOBAL_CHANDESC, + .length = S526_GPCT_CHANS, + .chans = { + {A4L_CHAN_AREF_GROUND, S526_GPCT_BITS}, + }, +}; + +static a4l_chdesc_t s526_chan_desc_ai = { + .mode = A4L_CHAN_GLOBAL_CHANDESC, + .length = S526_AI_CHANS, + .chans = { + {A4L_CHAN_AREF_GROUND, S526_AI_BITS}, + }, +}; + +static a4l_chdesc_t s526_chan_desc_ao = { + .mode = A4L_CHAN_GLOBAL_CHANDESC, + .length = S526_AO_CHANS, + .chans = { + {A4L_CHAN_AREF_GROUND, S526_AO_BITS}, + }, +}; + +static a4l_chdesc_t s526_chan_desc_dio = { + .mode = A4L_CHAN_GLOBAL_CHANDESC, + .length = S526_DIO_CHANS, + .chans = { + {A4L_CHAN_AREF_GROUND, S526_DIO_BITS}, + }, +}; + +/* --- Subdevice initialization functions --- */ + +/* General purpose counter/timer (gpct) */ +static void setup_subd_gpct(a4l_subd_t *subd) +{ + subd->flags = A4L_SUBD_COUNTER; + subd->chan_desc = &s526_chan_desc_gpct; + subd->insn_read = s526_gpct_rinsn; + subd->insn_config = s526_gpct_insn_config; + subd->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 */ + /* subd->do_cmd = s526_gpct_cmd; */ + /* subd->do_cmdtest = s526_gpct_cmdtest; */ + /* subd->cancel = s526_gpct_cancel; */ +} + +/* Analog input subdevice */ +static void setup_subd_ai(a4l_subd_t *subd) +{ + subd->flags = A4L_SUBD_AI; + subd->chan_desc = &s526_chan_desc_ai; + subd->rng_desc = &range_bipolar10; + subd->insn_read = s526_ai_rinsn; + subd->insn_config = s526_ai_insn_config; +} + +/* Analog output subdevice */ +static void setup_subd_ao(a4l_subd_t *subd) +{ + subd->flags = A4L_SUBD_AO; + subd->chan_desc = &s526_chan_desc_ao; + subd->rng_desc = &range_bipolar10; + subd->insn_write = s526_ao_winsn; + subd->insn_read = s526_ao_rinsn; +} + +/* Digital i/o subdevice */ +static void setup_subd_dio(a4l_subd_t *subd) +{ + if (S526_HAVE_DIO > 0) { + subd->flags = A4L_SUBD_DIO; + subd->chan_desc = &s526_chan_desc_dio; + subd->rng_desc = &range_digital; + subd->insn_bits = s526_dio_insn_bits; + subd->insn_config = s526_dio_insn_config; + } else { + subd->flags = A4L_SUBD_UNUSED; + } +} + +struct setup_subd { + void (*setup_func) (a4l_subd_t *); + int sizeof_priv; +}; + +static struct setup_subd setup_subds[4] = { + { + .setup_func = setup_subd_gpct, + .sizeof_priv = sizeof(struct s526_subd_gpct_priv), + }, + { + .setup_func = setup_subd_ai, + .sizeof_priv = sizeof(struct s526_subd_ai_priv), + }, + { + .setup_func = setup_subd_ao, + .sizeof_priv = sizeof(struct s526_subd_ao_priv), + }, + { + .setup_func = setup_subd_dio, + .sizeof_priv = sizeof(struct s526_subd_dio_priv), + }, +}; + +static int dev_s526_attach(a4l_dev_t *dev, a4l_lnkdesc_t *arg) +{ + int io_base; + int i; + int err = 0; + int n; + union cmReg cmReg; + + if (arg->opts == NULL || arg->opts_size < sizeof(unsigned long)) { + a4l_warn(dev, + "dev_s526_attach: no attach options specified." + "taking default options (addr=0x%x)\n", + S526_DEFAULT_ADDRESS); + + io_base = S526_DEFAULT_ADDRESS; + } else { + io_base = ((unsigned long *)arg->opts)[0]; + } + + if (!request_region(io_base, S526_IOSIZE, "s526")) { + a4l_err(dev, "dev_s526_attach: I/O port conflict"); + return -EIO; + } + + /* Allocate the subdevice structures. */ + for (i = 0; i < 4; i++) { + a4l_subd_t *subd = a4l_alloc_subd(setup_subds[i].sizeof_priv, + setup_subds[i].setup_func); + + if (subd == NULL) + return -ENOMEM; + + err = a4l_add_subd(dev, subd); + if (err != i) + return err; + } + + devpriv->io_base = io_base; + + a4l_info(dev, "dev_s526_attach: atached (address = 0x%x)\n", io_base); + + return 0; + +#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; + /* a4l_info(dev, "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); */ + /* a4l_info(dev, "Read back mode reg=0x%04x\n", */ + /* inw(ADDR_CHAN_REG(REG_C0M, n))); */ + + /* Load the pre-load register high word */ + + /* value = (short) (0x55); */ + /* outw(value, ADDR_CHAN_REG(REG_C0H, n)) */ + + /* Load the pre-load register low word */ + /* value = (short)(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); */ + /* a4l_info(dev, "Read back mode reg=0x%04x\n", */ + /* inw(ADDR_CHAN_REG(REG_C0M, n))); */ + +#endif + /* a4l_info(dev, "Current registres:\n"); */ + + /* for (i = 0; i < S526_NUM_PORTS; i++) { */ + /* a4l_info(dev, "0x%02lx: 0x%04x\n", */ + /* ADDR_REG(s526_ports[i]), inw(ADDR_REG(s526_ports[i]))); */ + /* } */ + return 0; +} + +static int dev_s526_detach(a4l_dev_t *dev) +{ + int err = 0; + + if (devpriv->io_base != 0) + release_region(devpriv->io_base, S526_IOSIZE); + + return err; +} + +static a4l_drv_t drv_s526 = { + .owner = THIS_MODULE, + .board_name = BOARD_NAME, + .attach = dev_s526_attach, + .detach = dev_s526_detach, + .privdata_size = sizeof(s526_priv_t), +}; + +static int __init drv_s526_init(void) +{ + return a4l_register_drv(&drv_s526); +} + +static void __exit drv_s526_cleanup(void) +{ + a4l_unregister_drv(&drv_s526); +} + +MODULE_DESCRIPTION("Analogy driver for Sensoray Model 526 board."); +MODULE_LICENSE("GPL"); + +module_init(drv_s526_init); +module_exit(drv_s526_cleanup); _______________________________________________ Xenomai-git mailing list Xenomai-git@gna.org https://mail.gna.org/listinfo/xenomai-git