--- /dev/null
+/*
+ * comedi/drivers/mf624.c
+ * Code for a Humusoft MF624 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: mf624.o
+Description: A comedi driver for Humusoft MF624 PCI Card
+Devices: Humusoft MF624 Multifunction I/O Card
+Author: Jean-Matthieu Bourgeot - Francois Poulain
+Updated: Wed, 19 Sep 2007 14:29:43 +0200
+Status: experimental
+
+This driver is a driver for the Humusoft MF624 PCI Card.
+
+It has :
+ * 8 channel 14 bits ADC,
+ * 8 channel 14 bits DAC,
+ * 8 digital inputs,
+ * 8 digital outputs,
+ * 4 quadrature encoder input,
+ * 5 timers/counter.
+
+Status:
+A/D Converter : Supported on subdevice 0,
+D/A Converter : Supported on subdevice 1,
+Digital Inputs : Supported on subdevice 2,
+Digital Outputs : Supported on subdevice 3,
+Counters/Timers : Supported on subdevice 4,
+Quadrature Encoder Inputs : Supported on subdevice 5,
+IRQ : Not yet supported.
+
+Remarks:
+Simultaneous D/A update not yet supported.
+
+Configuration Options:
+none
+ */
+
+#include <linux/comedidev.h>
+#include <linux/pci.h>
+#include "mf624.h"
+
+MODULE_AUTHOR("Francois Poulain <fpoulain AT gmail DOT com>");
+MODULE_DESCRIPTION("Humusoft MF624 Multifunction I/O Card");
+MODULE_LICENSE("GPL");
+
+/* Some definition ... */
+/* FIXME : adopt a REAL guideline for naming macros ... */
+/* FIXME : adopt a REAL guideline for presenting functions */
+/* FIXME : verify ALL types used in the functions */
+
+#define EXTDEBUG
+
+#define MF624_EXIT_FAILURE -1
+
+/* Addresses of the registers and usefull definitions
+ * for more informations, see the programming manual
+ * available near http://www2.humusoft.cz/www/datacq/manuals/mf624um.pdf */
+
+/* BADR0 Memory Map */
+#define MF624_GPIOC 0x54
+#define MF624_INTCSR 0x4C
+
+/* BADR1 Memory Map */
+#define MF624_ADCTRL 0x00
+#define MF624_ADDATA 0x00
+#define MF624_DIN 0x10
+#define MF624_DOUT 0x10
+#define MF624_ADSTART 0x20
+#define MF624_DA0 0x20
+
+/* BADR2 Memory Map */
+#define MF624_CTRXMODE 0x00
+#define MF624_CTRXSTATUS 0x00
+#define MF624_CTRXCTRL 0x60
+#define MF624_CTRXA 0x04
+#define MF624_CTRXDATA 0x04
+#define MF624_CTRXB 0x08
+#define MF624_IRCCTRL 0x6C
+#define MF624_IRCSTATUS 0x6C
+#define MF624_ENC_READ 0x70
+
+/* MASK */
+#define MF624_GPIOC_EOLC 0x00020000 /* End of last conversion, bit 17 */
+#define MF624_GPIOC_LDAC 0x00800000 /* Enable Load D/A converter, bit 23 */
+#define MF624_GPIOC_DACEN 0x04000000 /* Enable D/A outputs, bit 26 */
+
+/* Some constants for handling Counter Control Register */
+#define MF624_CTR_START 0x01
+#define MF624_CTR_STOP 0x02
+#define MF624_CTR_LOAD 0x04
+#define MF624_CTR_RESET 0x08
+#define MF624_CTR_OUT_SET 0x10
+#define MF624_CTR_OUT_RESET 0x20
+
+/* Multiply the following constant by one above to obtain global behavior on all timers' channels */
+#define MF624_CTR_GLOB 0x01041041
+
+/* Some constants for handling IRC Control Register (quadrature encoders) */
+/* All are not present, it's under heavy development */
+#define MF624_IRC_MODE_RAISING 0x01
+#define MF624_IRC_COUNT_ENABLE 0x00
+#define MF624_IRC_COUNT_DISABLE 0x04
+#define MF624_IRC_RESET_ENABLE 0x10
+#define MF624_IRC_RESET_DISABLE 0x00
+#define MF624_IRC_RESET_ON_TOP 0x30
+#define MF624_IRC_FILTER 0x80
+
+/* Multiply the following constant by one above to obtain global behavior on all encoders' channels */
+#define MF624_IRC_GLOB 0x01010101
+
+/* Structure for board description */
+typedef struct mf624_board_struct{
+ char *name;
+ unsigned short device_id;
+ unsigned int ai_chans;
+ unsigned int ai_bits;
+ unsigned int ao_chans;
+ unsigned int ao_bits;
+ unsigned int di_chans;
+ unsigned int do_chans;
+ unsigned int have_dio;
+ unsigned int timer_chans;
+ unsigned int timer_config[5];
+ unsigned int timer_loadA[5];
+ unsigned int timer_loadB[5];
+ unsigned int timer_control;
+ unsigned int enc_chans;
+ unsigned int enc_config;
+ unsigned int cnt_bits;
+}mf624_board;
+
+/* Board description */
+/* FIXME: timer_config, timer_control, and enc_config should be in the private structure */
+static const mf624_board mf624_boards[] = {
+ {
+ name: "mf624", /* device name */
+ device_id: 0x0624, /* PCI dev ID */
+ ai_chans: 8, /* Num of ADC channels */
+ ai_bits: 14, /* Num of ADC bits */
+ ao_chans: 8, /* Num of DAC channels */
+ ao_bits: 14, /* Num of DAC bits */
+ di_chans: 8, /* Num of digital in */
+ do_chans: 8, /* Num of digital out */
+ have_dio: 0, /* Num of DIO */
+ timer_chans: 5, /* Num of timers/cnt */
+ timer_config: {0,0,0,0}, /* Save of the timer configuration */
+ timer_loadA: {0,0,0,0}, /* Save of the timer loadA registers */
+ timer_loadB: {0,0,0,0}, /* Save of the timer loadB registers */
+ timer_control: 0, /* Save counter state control */
+ enc_chans: 6, /* Num of encoders : 4 + 1 virtual channel containing index register
+ + 1 virtual channel containing IRC status */
+ enc_config: 0, /* Save of the encoder configuration */
+ cnt_bits: 32, /* Size of timers/enc */
+ },
+};
+
+/* Number of boards */
+#define N_BOARDS (sizeof(mf624_boards) / sizeof(mf624_board))
+
+/* PCI vendor and device ID */
+#define PCI_VENDOR_ID_MF624 0x186c
+#define PCI_DEVICE_ID_MF624 0x0624
+
+static struct pci_device_id mf624_pci_table[] __devinitdata = {
+ { PCI_VENDOR_ID_MF624, PCI_DEVICE_ID_MF624, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, mf624_pci_table);
+
+/* Useful for shorthand access to the particular board structure */
+#define thisboard ((mf624_board *)dev->board_ptr)
+
+/* Private data structure */
+typedef struct{
+ int data;
+
+ /* would be useful for a PCI device */
+ struct pci_dev *pci_dev;
+
+ /* base addresses */
+ resource_size_t iobase;
+
+ unsigned long BADR0;
+ unsigned long BADR1;
+ unsigned long BADR2;
+
+ void* IO_BADR0;
+ void* IO_BADR1;
+ void* IO_BADR2;
+
+ /* Used for AO readback */
+ lsampl_t ao_readback[8];
+}mf624_private;
+
+/*
+ * most drivers define the following macro to make it easy to
+ * access the private structure.
+ */
+#define devpriv ((mf624_private *)dev->private)
+
+/* Attach/detach functions declaration */
+static int mf624_attach(comedi_device *dev,comedi_devconfig *it);
+static int mf624_detach(comedi_device *dev);
+
+/* Analog functions operation to be attached */
+static int mf624_ai_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_ai_cfg(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_ao_winsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_ao_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_ao_cfg(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+
+/* Digital functions operation to be attached */
+static int mf624_di_insn_bits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_do_insn_bits(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+
+/* Timers/counters functions operation to be attached */
+static int mf624_timer_winsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_timer_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_timer_cfg(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+
+/* Encoder functions operation to be attached */
+static int mf624_enc_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static int mf624_enc_cfg(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+
+/*static irqreturn_t mf624_interrupt(int irq, void *d, struct pt_regs *regs);*/
+
+/*
+ * 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_mf624={
+ driver_name: "mf624",
+ module: THIS_MODULE,
+ attach: mf624_attach,
+ detach: mf624_detach,
+};
+
+/*
+ * 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 mf624_attach(comedi_device *dev,comedi_devconfig *it){
+ comedi_subdevice *s;
+ struct pci_dev* pcidev;
+ unsigned int index, channel, status;
+
+ int regA, regB;
+
+ rt_printk("comedi%d: mf624: driver: Bourgeot - Poulain 2006-2007\n", dev->minor);
+ rt_printk("This is an experimental version, you can report some remarks or problems to fpoulain@gmail.com\n");
+
+ /*
+ * Allocate the private structure area. alloc_private() is a
+ * convenient macro defined in comedidev.h.
+ * FIXME : do this allocation in a dedicated function
+ */
+ if(alloc_private(dev,sizeof(mf624_private))<0) {return -ENOMEM;}
+
+ /* Probe the device to determine what device in the series it is */
+ 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)) {
+ /* is it not a computer boards card ? */
+ if(pcidev->vendor != PCI_VENDOR_ID_MF624){continue;}
+ /* loop through cards supported by this driver */
+ for(index = 0; index < N_BOARDS ; index++)
+ {
+ if(mf624_boards[index].device_id != pcidev->device){continue;}
+ /* was a particular bus/slot requested ? */
+ if(it->options[0] || it->options[1])
+ {
+ /* are we on the wrong bus/slot ? */
+ if(pcidev->bus->number != it->options[0] ||
+ PCI_SLOT(pcidev->devfn) != it->options[1]){continue;}
+ }
+ devpriv->pci_dev = pcidev;
+ dev->board_ptr = mf624_boards + index;
+ goto found;
+ }
+ }
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: No supported Humusoft card found on requested position\n", dev->minor);
+#endif
+ comedi_error(dev, "No supported Humusoft card found on requested position\n");
+ return -EIO;
+
+found:
+ rt_printk("comedi%d: mf624: Found %s on bus %i, slot %i\n", dev->minor, mf624_boards[index].name, pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+
+ /* Enable PCI device and reserve I/O ports. */
+ if(pci_enable_device(pcidev)){
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: Failed to enable PCI device\n", dev->minor);
+#endif
+ comedi_error(dev, "Failed to enable PCI device\n");
+ return -EIO;
+ }
+ if(pci_request_regions(pcidev, "mf624")){
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: I/O port conflict\n", dev->minor);
+#endif
+ comedi_error(dev, "I/O port conflict\n");
+ return -EIO;
+ }
+
+ /* Initialize devpriv->control_status and to point to their base address */
+ devpriv->BADR0 = pci_resource_start(devpriv->pci_dev, 0);
+ devpriv->BADR1 = pci_resource_start(devpriv->pci_dev, 1);
+ devpriv->BADR2 = pci_resource_start(devpriv->pci_dev, 2);
+
+ devpriv->IO_BADR0 = ioremap_nocache(pci_resource_start(devpriv->pci_dev, 0),
+ pci_resource_len(devpriv->pci_dev, 0));
+ devpriv->IO_BADR1 = ioremap_nocache(pci_resource_start(devpriv->pci_dev, 2),
+ pci_resource_len(devpriv->pci_dev, 2));
+ devpriv->IO_BADR2 = ioremap_nocache(pci_resource_start(devpriv->pci_dev, 4),
+ pci_resource_len(devpriv->pci_dev, 4));
+
+#ifdef EXTDEBUG
+ rt_printk ( "comedi%d: mf624: PCI Resource 0 addr %lx \n", dev->minor, devpriv->BADR0 );
+ rt_printk ( "comedi%d: mf624: PCI Resource 1 addr %lx \n", dev->minor, devpriv->BADR1 );
+ rt_printk ( "comedi%d: mf624: PCI Resource 2 addr %lx \n", dev->minor, devpriv->BADR2 );
+
+ rt_printk ( "comedi%d: mf624: IO_BADR0 addr %p \n", dev->minor, devpriv->IO_BADR0 );
+ rt_printk ( "comedi%d: mf624: IO_BADR1(2) addr %p \n", dev->minor, devpriv->IO_BADR1 );
+ rt_printk ( "comedi%d: mf624: IO_BADR2(4) addr %p \n", dev->minor, devpriv->IO_BADR2 );
+#endif
+
+ /*
+ * Initialize dev->board_name. Note that we can use the "thisboard"
+ * macro now, since we just initialized it in the last line.
+ */
+ dev->board_name = thisboard->name;
+
+ /*
+ * Allocate the subdevice structures. alloc_subdevice() is a
+ * convenient macro defined in comedidev.h.
+ */
+ if(alloc_subdevices(dev, 6)<0)
+ return -ENOMEM;
+
+ s=dev->subdevices+0;
+ /* analog single ended (ground) input subdevice */
+ s->type = COMEDI_SUBD_AI;
+ s->subdev_flags = SDF_READABLE|SDF_GROUND;
+ s->n_chan = thisboard->ai_chans;
+ s->maxdata = (1<<thisboard->ai_bits)-1;
+ s->range_table = &range_bipolar10;
+ s->len_chanlist = 8;
+ s->insn_read = mf624_ai_rinsn;
+ s->insn_config = mf624_ai_cfg;
+
+ s=dev->subdevices+1;
+ /* 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 = &range_bipolar10;
+ s->insn_write = mf624_ao_winsn;
+ s->insn_read = mf624_ao_rinsn;
+ s->insn_config = mf624_ao_cfg;
+
+ s=dev->subdevices+2;
+ /* digital input subdevice */
+ s->type = COMEDI_SUBD_DI;
+ s->subdev_flags = SDF_READABLE;
+ s->n_chan = thisboard->di_chans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf624_di_insn_bits;
+
+ s=dev->subdevices+3;
+ /* digital output subdevice */
+ s->type = COMEDI_SUBD_DO;
+ s->subdev_flags = SDF_WRITABLE;
+ s->n_chan = thisboard->do_chans;
+ s->maxdata = 1;
+ s->range_table = &range_digital;
+ s->insn_bits = mf624_do_insn_bits;
+
+ s=dev->subdevices+4;
+ /* timer device */
+ s->type = COMEDI_SUBD_TIMER;
+ s->subdev_flags = SDF_WRITABLE|SDF_READABLE;
+ s->n_chan = thisboard->timer_chans;
+ s->maxdata = (1<<thisboard->cnt_bits)-1;
+ s->range_table = &range_digital;
+ s->insn_config = mf624_timer_cfg;
+ s->insn_write = mf624_timer_winsn;
+ s->insn_read = mf624_timer_rinsn;
+
+ s=dev->subdevices+5;
+ /* encoder device */
+ s->type = COMEDI_SUBD_COUNTER;
+ s->subdev_flags = SDF_READABLE|SDF_GROUND|SDF_DIFF;
+ s->n_chan = thisboard->enc_chans;
+ s->maxdata = (1<<thisboard->cnt_bits)-1;
+ s->range_table = &range_digital;
+ s->insn_config = mf624_enc_cfg;
+ s->insn_read = mf624_enc_rinsn;
+
+ rt_printk("comedi%d: mf624: Driver attached\n", dev->minor);
+
+ /* FIXME : do those initialisation in some dedicated functions */
+
+ /* Enable DAC */
+ status = readl(devpriv->IO_BADR0 + MF624_GPIOC) | MF624_GPIOC_DACEN;
+ writel(status, devpriv->IO_BADR0 + MF624_GPIOC);
+
+ /* Initialise Interrupt Control Status */
+ writel(0x00000000, devpriv->IO_BADR0 + MF624_INTCSR);
+
+ /* Initialise analog outputs to zero */
+ for(channel=0 ; channel<thisboard->ao_chans ; channel++){
+ writew(0x2000,devpriv->IO_BADR1 + MF624_DA0 + 2*channel);
+ /* Readback save */
+ devpriv->ao_readback[channel] = 0x2000;
+ }
+
+ /* Initialise counters to initial state and reset them */
+ for(channel=0 ; channel<thisboard->timer_chans ; channel++){
+ /* Restore initial state */
+ writel(0x00000020, devpriv->IO_BADR2 + MF624_CTRXMODE + 0x10*channel);
+ /* Reset A register */
+ writel(0x00000000, devpriv->IO_BADR2 + MF624_CTRXB + 0x10*channel);
+ /* Reset B register (don't exist for timer 4) */
+ if(channel!=4) {writel(0x00000000, devpriv->IO_BADR2 + MF624_CTRXB + 0x10*channel);}
+ }
+ /* Reset and stop all timers in one shot !*/
+ writel((MF624_CTR_RESET|MF624_CTR_OUT_RESET|MF624_CTR_STOP) * MF624_CTR_GLOB, devpriv->IO_BADR2 + MF624_CTRXCTRL);
+
+ /* test output 1kHz frequency */
+ /* calc preset register : Reg = (long)(1<<chanBits) -
+ Fclk/Fout */
+#define RAPPORT_CYCLIQUE 0.5
+#define PERIODE 0.001
+ regA = (long)(1<<32) - 100000*PERIODE*RAPPORT_CYCLIQUE;
+ regB = (long)(1<<32) - 100000*PERIODE*(1-RAPPORT_CYCLIQUE);
+ rt_printk("Preset registers for PWM output : A = %x and B = %x \n", regA, regB);
+ /* load preset in A reg */
+ writel(regA, devpriv->IO_BADR2 + MF624_CTRXA);
+ /* load preset/2 in B reg */
+ writel(regB, devpriv->IO_BADR2 + MF624_CTRXB);
+ /* Configure CTR0 */
+ writel(0x0000C00F, devpriv->IO_BADR2 + MF624_CTRXMODE);
+ /* Load register and start counter 0 */
+ writel(MF624_CTR_LOAD|MF624_CTR_START, devpriv->IO_BADR2 + MF624_CTRXCTRL);
+ rt_printk("Counter 0 status = %x\n", readl(devpriv->IO_BADR2));
+
+ /* Stop and keep reset all encoders */
+ thisboard->enc_config = (MF624_IRC_COUNT_DISABLE|MF624_IRC_RESET_ENABLE) * MF624_IRC_GLOB;
+ writel(thisboard->enc_config, devpriv->IO_BADR2 + MF624_IRCCTRL);
+
+ /* End of initialisation */
+ rt_printk("comedi%d: mf624: Board initialized\n", dev->minor);
+ 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 mf624_detach(comedi_device *dev)
+{
+ rt_printk("comedi%d: mf624: remove\n", dev->minor);
+
+ if(dev->irq){comedi_free_irq(dev->irq, dev);}
+
+ if(devpriv && devpriv->pci_dev)
+ {
+ if(devpriv->BADR0)
+ {
+ pci_release_regions(devpriv->pci_dev);
+ pci_disable_device(devpriv->pci_dev);
+ rt_printk("comedi%d: mf624: remove2\n", dev->minor);
+ }
+ pci_dev_put(devpriv->pci_dev);
+ rt_printk("comedi%d: mf624: remove3\n", dev->minor);
+ }
+return 0;
+}
+
+/* read n samples on Analog Input channel */
+static int mf624_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ unsigned int dat, n, i, status, chan = CR_CHAN(insn->chanspec);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: mf624_ai_rinsn called \n", dev->minor);
+#endif
+
+ /* write channel to multiplexer */
+ writew(1<<chan,devpriv->IO_BADR1 + MF624_ADCTRL);
+
+ /* convert n samples */
+ for(n=0;n<insn->n;n++){
+ /* trigger conversion */
+ dat = readw(devpriv->IO_BADR1 + MF624_ADSTART);
+
+#define TIMEOUT 100
+ /* wait for conversion to end */
+ for(i=0;i<TIMEOUT;i++){
+ status = 1;
+ status = readl(devpriv->IO_BADR0 + MF624_GPIOC);
+ if(!(status & MF624_GPIOC_EOLC))break;
+ }
+ if(i==TIMEOUT){
+ rt_printk("comedi%d: mf624: _ai_rinsn: conversion timeout !\n", dev->minor);
+ comedi_error(dev, "Conversion timeout !\n");
+ return -ETIMEDOUT;
+ }
+
+ /* read data */
+ dat = readw(devpriv->IO_BADR1 + MF624_ADDATA);
+
+ /* mangle the data as necessary */
+ dat ^= 1<<(thisboard->ai_bits-1);
+
+ data[n] = dat;
+ }
+
+ /* return the number of samples read/written */
+ return n;
+}
+
+/* Analog input configuration */
+static int mf624_ai_cfg(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data){
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _insn_ai_cfg called\n", dev->minor);
+#endif
+ return insn->n;
+}
+
+/* write n samples on Analog Output channel */
+static int mf624_ao_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ unsigned int i, chan = CR_CHAN(insn->chanspec), status;
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _ao_winsn called\n", dev->minor);
+#endif
+ status = readl(devpriv->IO_BADR0 + MF624_GPIOC);
+ status = status | MF624_GPIOC_DACEN;
+ writel(status, devpriv->IO_BADR0 + MF624_GPIOC);
+ /* 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 */
+ writew(data[i],devpriv->IO_BADR1 + MF624_DA0 + 2*chan);
+ devpriv->ao_readback[chan] = data[i];
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _ao_winsn: wrote at address %i data %i ", dev->minor, MF624_DA0 + 2*chan , data[i] );
+#endif
+ }
+
+ /* return the number of samples read/written */
+ return i;
+}
+
+/* Analog output configuration */
+static int mf624_ao_cfg(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data){
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _ao_cfg called\n, dev->minor");
+#endif
+ return insn->n;
+}
+
+/* AO subdevices should have a read insn as well as a write insn.
+ * Usually this means copying a value stored in devpriv. */
+static int mf624_ao_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data)
+{
+ unsigned int i, chan = CR_CHAN(insn->chanspec);
+
+ for(i=0;i<insn->n;i++)
+ data[i] = devpriv->ao_readback[chan];
+
+ return i;
+}
+
+/* Write digital data */
+static int mf624_do_insn_bits(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn,lsampl_t *data)
+{
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _do_insn_bits called with data: %d %d\n", dev->minor, data[0], data[1]);
+#endif
+ if(insn->n!=2)return -EINVAL;
+
+ if(data[0]){
+ s->state &= ~data[0];
+ s->state |= data[0]&data[1];
+#ifdef EXTDEBUG
+ rt_printk ("comedi%d: mf624: _do_insn_bits: out: %d on %lx\n", dev->minor, s->state, devpriv->IO_BADR1 + MF624_DOUT );
+#endif
+ writew(s->state, devpriv->IO_BADR1 + MF624_DOUT);
+ }
+ return 2;
+}
+
+/* Read digital data */
+static int mf624_di_insn_bits(comedi_device *dev,comedi_subdevice *s, comedi_insn *insn,lsampl_t *data)
+{
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _di_insn_bits called with data: %d %d\n", dev->minor, data[0], data[1]);
+#endif
+ if(insn->n!=2)return -EINVAL;
+
+ data[1] = readw(devpriv->IO_BADR1 + MF624_DIN );
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _di_insn_bits read data: %d\n", dev->minor, data[1]);
+#endif
+ return 2;
+}
+
+/* Set the preload registers */
+static int mf624_timer_winsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data){
+ unsigned int channel = CR_CHAN(insn->chanspec);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_winsn called for channel %d, with n = %d\n", dev->minor, channel, insn->n);
+#endif
+
+ if(insn->n > 2)return -EINVAL;
+
+ thisboard->timer_loadA[channel] = data[0];
+ writel(thisboard->timer_loadA[channel], devpriv->IO_BADR2 + MF624_CTRXA + 0x10*channel);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_winsn: preset register A set at %8x\n", dev->minor, thisboard->timer_loadA[channel]);
+#endif
+
+ if(insn->n == 2){
+ thisboard->timer_loadB[channel] = data[1];
+ writel(thisboard->timer_loadB[channel], devpriv->IO_BADR2 + MF624_CTRXB + 0x10*channel);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_winsn: preset register B set at %8x\n", dev->minor, thisboard->timer_loadB[channel]);
+#endif
+ }
+
+ return insn->n;
+}
+
+/* Read the timer or the preload registers */
+static int mf624_timer_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data){
+ unsigned int channel = CR_CHAN(insn->chanspec), i = 0, status = 0, loadA = 0, loadB = 0, config = 0;
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_rinsn called for channel %d, with n = %d\n", dev->minor, channel, insn->n);
+#endif
+
+ if(insn->n == 4){
+ /* Read the counter status, the preloads registers and the counter configuration */
+ config = thisboard->timer_config[channel];
+ loadA = thisboard->timer_loadA[channel];
+ loadB = thisboard->timer_loadB[channel];
+ status = readl(devpriv->IO_BADR2 + MF624_CTRXSTATUS + 0x10*channel);
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_rinsn: status = %8x, loadA = %8x, loadB = %8x, config = %8x\n", dev->minor, status, loadA, loadB, config);
+#endif
+
+ data[0] = status;
+ data[1] = loadA;
+ data[2] = loadB;
+ data[3] = config;
+ }
+ else
+ for(i = 0 ; i < insn->n ; i++) data[i] = readl(devpriv->IO_BADR2 + MF624_CTRXDATA + 0x10*channel);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_rinsn: readed %x\n", dev->minor, data[insn->n-1]);
+#endif
+ return insn->n;
+}
+
+/* Configure timers */
+static int mf624_timer_cfg(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data){
+
+ unsigned int bitfield = 0, bitfield2 = 0, status = 0, mask = 0, mask2 = 0, i = 0, dataIndex = 0, channel = CR_CHAN(insn->chanspec);
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_cfg called with %d data for channel %d and datas : ", dev->minor, insn->n, channel);
+ for(i=0 ; i < insn->n ; i++) rt_printk("%x ", data[i]);
+ rt_printk("\n");
+#endif
+
+ /* For more informations, see the programming manual
+ * available near http://www2.humusoft.cz/www/datacq/manuals/mf624um.pdf */
+
+ /* FIXME : test and fix if maximum one opcode is given for each subconfiguration */
+ /* FIXME : thereis an *nicer* way to do this, using some unnecessary flags defined in mf624.h */
+ for(dataIndex = 0 ; dataIndex < insn->n ; dataIndex++){
+ switch(data[dataIndex]){
+ /* Mode register configuration */
+
+ /* Configure direction */
+ case MF624_CTR_DIRECTION_DOWN : bitfield |= 0x00; mask |= 0x01; break ;
+ case MF624_CTR_DIRECTION_UP : bitfield |= 0x01; mask |= 0x01; break ;
+
+ /* Configure repetition */
+ case MF624_CTR_REPETITION_OFF : bitfield |= 0x00<<1; mask |= 0x01<<1; break ;
+ case MF624_CTR_REPETITION_ON : bitfield |= 0x01<<1; mask |= 0x01<<1; break ;
+
+ /* Configure load toggle */
+ case MF624_CTR_PRELOAD_A : bitfield |= 0x00<<2; mask |= 0x01<<2; break ;
+ case MF624_CTR_PRELOAD_AB : bitfield |= 0x01<<2; mask |= 0x01<<2; break ;
+
+ /* Configure output toggle */
+ case MF624_CTR_OUTPUT_MONOSTABLE : bitfield |= 0x00<<3; mask |= 0x01<<3; break ;
+ case MF624_CTR_OUTPUT_BISTABLE : bitfield |= 0x01<<3; mask |= 0x01<<3; break ;
+
+ /* Configure output control */
+ case MF624_CTR_OUTPUT_DIRECT : bitfield |= 0x00<<4; mask |= 0x03<<4; break ;
+ case MF624_CTR_OUTPUT_INVERSE : bitfield |= 0x01<<4; mask |= 0x03<<4; break ;
+ case MF624_CTR_OUTPUT_OFF : bitfield |= 0x02<<4; mask |= 0x03<<4; break ;
+ case MF624_CTR_OUTPUT_ON : bitfield |= 0x03<<4; mask |= 0x03<<4; break ;
+
+ /* Configure trigger source */
+ case MF624_CTR_TRIGGER_DISABLED : bitfield |= 0x00<<6; mask |= 0x03<<6; break ;
+ case MF624_CTR_TRIGGER_BYINPUT : bitfield |= 0x01<<6; mask |= 0x03<<6; break ;
+ case MF624_CTR_TRIGGER_BYLOWER : bitfield |= 0x02<<6; mask |= 0x03<<6; break ;
+ case MF624_CTR_TRIGGER_BYUPPER : bitfield |= 0x03<<6; mask |= 0x03<<6; break ;
+
+ /* Configure trigger type */
+ case MF624_CTR_TRIGGER_DISABLE : bitfield |= 0x00<<8; mask |= 0x03<<8; break ;
+ case MF624_CTR_TRIGGER_RAISING : bitfield |= 0x01<<8; mask |= 0x03<<8; break ;
+ case MF624_CTR_TRIGGER_FALLING : bitfield |= 0x02<<8; mask |= 0x03<<8; break ;
+ case MF624_CTR_TRIGGER_EITHER : bitfield |= 0x03<<8; mask |= 0x03<<8; break ;
+
+ /* Configure retrigger */
+ case MF624_CTR_RETRIGGER_OFF : bitfield |= 0x00<<10; mask |= 0x01<<10; break ;
+ case MF624_CTR_RETRIGGER_ON : bitfield |= 0x01<<10; mask |= 0x01<<10; break ;
+
+ /* Configure gate source */
+ case MF624_CTR_GATE_HIGHT : bitfield |= 0x00<<11; mask |= 0x03<<11; break ;
+ case MF624_CTR_GATE_BYINPUT : bitfield |= 0x01<<11; mask |= 0x03<<11; break ;
+ case MF624_CTR_GATE_BYLOWER : bitfield |= 0x02<<11; mask |= 0x03<<11; break ;
+ case MF624_CTR_GATE_BYUPPER : bitfield |= 0x03<<11; mask |= 0x03<<11; break ;
+
+ /* Configure gate polarity */
+ case MF624_CTR_GATEPOLARITY_LOW : bitfield |= 0x00<<13; mask |= 0x01<<13; break ;
+ case MF624_CTR_GATEPOLARITY_HIGH : bitfield |= 0x01<<13; mask |= 0x01<<13; break ;
+
+ /* Configure clock source */
+ case MF624_CTR_CKSOURCE_50M : bitfield |= 0x00<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_10M : bitfield |= 0x01<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_1M : bitfield |= 0x02<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_100K : bitfield |= 0x03<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_RAISINGINPUT : bitfield |= 0x05<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_FALLINGINPUT : bitfield |= 0x06<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_EITHERINPUT : bitfield |= 0x07<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_RAISINGLOWER : bitfield |= 0x09<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_FALLINGLOWER : bitfield |= 0x0A<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_EITHERLOWER : bitfield |= 0x0B<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_RAISINGUPPER : bitfield |= 0x0D<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_FALLINGUPPER : bitfield |= 0x0E<<14; mask |=0x0F<<14 ; break ;
+ case MF624_CTR_CKSOURCE_EITHERUPPER : bitfield |= 0x0F<<14; mask |=0x0F<<14 ; break ;
+
+ /* Configure analog trigger */
+ case MF624_CTR_ADTRIGSRC_INT : bitfield |= 0x00<<30; mask |= 0x01<<30; break ;
+ case MF624_CTR_ADTRIGSRC_EXT : bitfield |= 0x01<<30; mask |= 0x01<<30; break ;
+
+ /* Configure CTR4 interrupt source */
+ case MF624_CTR_CTR4INTSRC_INT : bitfield |= 0x00<<31; mask |= 0x01<<31; break ;
+ case MF624_CTR_CTR4INTSRC_EXT : bitfield |= 0x01<<31; mask |= 0x01<<31; break ;
+
+ /* Control register affection */
+ case MF624_CTR_CTRL_START : bitfield2 |= 0x01; break;
+ case MF624_CTR_CTRL_STOP : bitfield2 |= 0x02; break;
+ case MF624_CTR_CTRL_LOAD : bitfield2 |= 0x04; break;
+ case MF624_CTR_CTRL_RESET : bitfield2 |= 0x08; break;
+ case MF624_CTR_CTRL_TSET : bitfield2 |= 0x10; break;
+ case MF624_CTR_CTRL_TRESET : bitfield2 |= 0x20; break;
+
+ /* Error handling */
+ default : return MF624_EXIT_FAILURE;
+ }
+ }
+ status = thisboard->timer_config[channel];
+ mask = ~mask;
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_cfg: bitfield = %8x, mask = %8x, status = %8x\n", dev->minor, bitfield, mask, status);
+ rt_printk("comedi%d: mf624: _timer_cfg: writing %8x in %8x \n", dev->minor, (status & mask) | bitfield, devpriv->IO_BADR2 + MF624_CTRXMODE + 0x10*channel);
+#endif
+
+ thisboard->timer_config[channel] = (status & mask) | bitfield;
+ if (channel != 4 && (thisboard->timer_config[channel] & 0xC0000000) !=0){
+ rt_printk("comedi%d: mf624: _timer_cfg: Only 4th counter implement those features!\n", dev->minor);
+ comedi_error(dev, "Only 4th counter implement those features!\n");
+ }
+ writel(thisboard->timer_config[channel], devpriv->IO_BADR2 + MF624_CTRXMODE + 0x10*channel);
+
+ mask2 = ~(0x3F << (6*channel));
+ status = thisboard->timer_control;
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _timer_cfg: bitfield2 = %8x, mask2 = %8x, status2 = %8x\n", dev->minor, bitfield2, mask2, status);
+ rt_printk("comedi%d: mf624: _timer_cfg: writing %8x in %8x \n", dev->minor, (status & mask2) | (bitfield2 << (6*channel)), devpriv->IO_BADR2 + MF624_CTRXCTRL);
+#endif
+
+ thisboard->timer_control = (status & mask2) + (bitfield << (6*channel));
+ writel(thisboard->timer_control, devpriv->IO_BADR2 + MF624_CTRXCTRL);
+
+ return insn->n;
+}
+
+/* Read an encoder */
+static int mf624_enc_rinsn(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data){
+ unsigned int i, channel = CR_CHAN(insn->chanspec);
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _enc_rinsn called for channel %d, with n = %d\n", dev->minor, channel, insn->n);
+#endif
+ for(i=0;i<insn->n;i++){
+ if(channel == (thisboard->enc_chans - 1)){
+ /* virtual channel 5 : read index register */
+ data[i] = readl(devpriv->IO_BADR2 + MF624_IRCSTATUS) & 0x01010101;
+ }
+ if(channel == (thisboard->enc_chans - 2)){
+ /* virtual channel 4 : read configuration register */
+ data[i] = thisboard->enc_config;
+ }
+ else
+ data[i] = readl(devpriv->IO_BADR2 + MF624_ENC_READ + 0x04*channel);
+ }
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _enc_rinsn: readed %x\n", dev->minor, data[insn->n-1]);
+#endif
+
+ return insn->n;
+}
+
+/* Configure encoders */
+static int mf624_enc_cfg(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data){
+
+ unsigned char bitfield = 0;
+ unsigned int status = 0, mask = 0, i = 0, dataIndex = 0, channel = CR_CHAN(insn->chanspec);
+
+ /* You cannot configure virtual channel */
+ if(channel >= (thisboard->enc_chans - 2)){
+ rt_printk("comedi%d: mf624: trying to configure virtual channel!\n", dev->minor);
+ comedi_error(dev, "Trying to configure virtual channel!\n");
+ return MF624_EXIT_FAILURE;
+ }
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _enc_cfg called with %d data for channel %d and datas : ", dev->minor, insn->n, channel);
+ for(i=0 ; i < insn->n ; i++) rt_printk("%x ", data[i]);
+ rt_printk("\n");
+#endif
+
+ /* For more informations, see the programming manual
+ * available near http://www2.humusoft.cz/www/datacq/manuals/mf624um.pdf */
+
+ /* FIXME : test and fix if maximum one opcode is given for each subconfiguration */
+ for(dataIndex = 0 ; dataIndex < insn->n ; dataIndex++){
+ switch(data[dataIndex]){
+ /* Configure encoder mode */
+ case MF624_ENC_MODE_4EDGE : bitfield |= 0x00 ; break ;
+ case MF624_ENC_MODE_RISING : bitfield |= 0x01 ; break ;
+ case MF624_ENC_MODE_FALLING : bitfield |= 0x02 ; break ;
+ case MF624_ENC_MODE_EITHER : bitfield |= 0x03 ; break ;
+
+ /* Configure encoder counting */
+ case MF624_ENC_COUNT_ENABLE : bitfield |= 0x00 ; break ;
+ case MF624_ENC_COUNT_DISABLE : bitfield |= 0x04 ; break ;
+ case MF624_ENC_COUNT_ENABLE_BYINPUT : bitfield |= 0x08 ; break ;
+ case MF624_ENC_COUNT_DISABLE_BYINPUT : bitfield |= 0x0C ; break ;
+
+ /* Configure encoder reset */
+ case MF624_ENC_RESET_DISABLE : bitfield |= 0x00 ; break ;
+ case MF624_ENC_RESET_ENABLE : bitfield |= 0x10 ; break ;
+ case MF624_ENC_RESET_DISABLE_BYINPUT : bitfield |= 0x20 ; break ;
+ case MF624_ENC_RESET_ENABLE_BYINPUT : bitfield |= 0x30 ; break ;
+ case MF624_ENC_RESET_ONRAISING_INPUT : bitfield |= 0x40 ; break ;
+ case MF624_ENC_RESET_ONFALLING_INPUT : bitfield |= 0x50 ; break ;
+ case MF624_ENC_RESET_ONEITHER_INPUT : bitfield |= 0x60 ; break ;
+
+ /* Configure encoder reset */
+ case MF624_ENC_FILTER_OFF : bitfield |= 0x00 ; break ;
+ case MF624_ENC_FILTER_ON : bitfield |= 0x80 ; break ;
+
+ /* Error handling */
+ default : return MF624_EXIT_FAILURE;
+ }
+ }
+
+ mask = ~(0xFF << (8*channel));
+ status = thisboard->enc_config;
+
+#ifdef EXTDEBUG
+ rt_printk("comedi%d: mf624: _enc_cfg: bitfield = %8x, mask = %8x, status = %8x\n", dev->minor, bitfield, mask, status);
+ rt_printk("comedi%d: mf624: _enc_cfg: writing %8x in %8x \n", dev->minor, (status & mask) + (bitfield << (8*channel)), devpriv->IO_BADR2 + MF624_IRCCTRL);
+#endif
+
+ thisboard->enc_config = (status & mask) + (bitfield << (8*channel));
+ writel(thisboard->enc_config, devpriv->IO_BADR2 + MF624_IRCCTRL);
+
+ return insn->n;
+}
+
+/*
+ * A convenient macro that defines init_module() and cleanup_module(),
+ * as necessary.
+ */
+COMEDI_INITCLEANUP(driver_mf624);