text: /*
      comedi/drivers/ni_670x.c
      Hardware driver for NI 670x devices
  
      COMEDI - Linux Control and Measurement Device Interface
      Copyright (C) 1997-2001 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: ni_670x
  Description: National Instruments 670x
  Author: Bart Joris <bjoris@advalvas.be>
  Updated: Wed, 11 Dec 2002 18:25:35 -0800
  Devices: [National Instruments] PCI-6703 (ni_670x), PCI-6704
  Status: unknown
  
  Commands are not supported.
  */
  
  /*
  	Bart Joris <bjoris@advalvas.be> Last updated on 20/08/2001
  
  	Manuals:
  
  	322110a.pdf	PCI/PXI-6704 User Manual
  	322110b.pdf	PCI/PXI-6703/6704 User Manual
  */
  
  /*
   * Integration with Xenomai/Analogy layer based on the
   * comedi driver. Adaptation made by
   *   Julien Delange <julien.delange@esa.int>
   */
  
  #include <linux/interrupt.h>
  #include <linux/slab.h>
  #include <rtdm/analogy/device.h>
  
  #include "../intel/8255.h"
  #include "ni_mio.h"
  #include "mite.h"
  
  #define PCIMIO_IRQ_POLARITY 1
  
  #define  AO_VALUE_OFFSET         0x00
  #define  AO_CHAN_OFFSET          0x0c
  #define  AO_STATUS_OFFSET        0x10
  #define  AO_CONTROL_OFFSET       0x10
  #define  DIO_PORT0_DIR_OFFSET    0x20
  #define  DIO_PORT0_DATA_OFFSET   0x24
  #define  DIO_PORT1_DIR_OFFSET    0x28
  #define  DIO_PORT1_DATA_OFFSET   0x2c
  #define  MISC_STATUS_OFFSET      0x14
  #define  MISC_CONTROL_OFFSET     0x14
  
  /* Board description*/
  
  struct ni_670x_board {
  	unsigned short device_id;
  	const char *name;
  	unsigned short ao_chans;
  	unsigned short ao_bits;
  };
  
  #define thisboard ((struct ni_670x_board *)dev->board_ptr)
  
  struct ni_670x_private {
  	struct mite_struct *mite;
  	int boardtype;
  	int dio;
  	unsigned int ao_readback[32];
  
  	/*
  	 * Added when porting to xenomai
  	 */
  	int irq_polarity;
  	int irq_pin;
  	int irq;
  	struct ni_670x_board *board_ptr;
  	/*
  	 * END OF ADDED when porting to xenomai
  	 */
  };
  
  struct ni_670x_subd_priv {
  	int io_bits;
  	unsigned int state;
  	uint16_t readback[2];
  	uint16_t config;
  	void* counter;
  };
  
  static int ni_670x_ao_winsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn);
  static int ni_670x_ao_rinsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn);
  static int ni_670x_dio_insn_bits(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn);
  static int ni_670x_dio_insn_config(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn);
  
  static struct a4l_channels_desc ni_670x_desc_dio = {
  	.mode = A4L_CHAN_GLOBAL_CHANDESC,
  	.length = 8,
  	.chans = {
  		{A4L_CHAN_AREF_GROUND, 1},
  	},
  };
  
  static struct a4l_channels_desc ni_670x_desc_ao = {
  	.mode = A4L_CHAN_GLOBAL_CHANDESC,
  	.length = 0, /* initialized later according to the board found */
  	.chans = {
  		{A4L_CHAN_AREF_GROUND, 16},
  	},
  };
  
  
  static struct a4l_rngtab range_0_20mA = { 1, {RANGE_mA(0, 20)} };
  static struct a4l_rngtab rng_bipolar10 = { 1, {RANGE_V(-10, 10) }};
  
  struct a4l_rngtab *range_table_list[32] = {
  	&rng_bipolar10, &rng_bipolar10, &rng_bipolar10, &rng_bipolar10,
  	&rng_bipolar10, &rng_bipolar10, &rng_bipolar10, &rng_bipolar10,
  	&rng_bipolar10, &rng_bipolar10, &rng_bipolar10, &rng_bipolar10,
  	&rng_bipolar10, &rng_bipolar10, &rng_bipolar10, &rng_bipolar10,
  	&range_0_20mA, &range_0_20mA, &range_0_20mA, &range_0_20mA,
  	&range_0_20mA, &range_0_20mA, &range_0_20mA, &range_0_20mA,
  	&range_0_20mA, &range_0_20mA, &range_0_20mA, &range_0_20mA,
  	&range_0_20mA, &range_0_20mA, &range_0_20mA, &range_0_20mA};
  
  static A4L_RNGDESC(32) ni670x_ao_desc;
  
  static void setup_subd_ao(struct a4l_subdevice *subd)
  {
  	int i;
  	int nchans;
  
  	nchans = ((struct ni_670x_private*)(subd->dev->priv))->board_ptr->ao_chans;
  	subd->flags                = A4L_SUBD_AO;
  	subd->chan_desc            = &ni_670x_desc_ao;
  	subd->chan_desc->length    = nchans;
  	if (nchans == 32) {
  
  		subd->rng_desc = (struct a4l_rngdesc*) &ni670x_ao_desc;
  		subd->rng_desc->mode = A4L_RNG_PERCHAN_RNGDESC;
  		for (i = 0 ; i < 16 ; i++) {
  			subd->rng_desc->rngtabs[i] =&rng_bipolar10;
  			subd->rng_desc->rngtabs[16+i] =&range_0_20mA;
  		}
  	} else
  		subd->rng_desc = &a4l_range_bipolar10;
  
  	subd->insn_write = &ni_670x_ao_winsn;
  	subd->insn_read = &ni_670x_ao_rinsn;
  }
  
  static void setup_subd_dio(struct a4l_subdevice *s)
  {
  	/* Digital i/o subdevice */
  	s->flags = A4L_SUBD_DIO;
  	s->chan_desc = &ni_670x_desc_dio;
  	s->rng_desc = &range_digital;
  	s->insn_bits = ni_670x_dio_insn_bits;
  	s->insn_config = ni_670x_dio_insn_config;
  }
  
  struct setup_subd {
  	void (*setup_func) (struct a4l_subdevice *);
  	int sizeof_priv;
  };
  
  static struct setup_subd setup_subds[2] = {
  	{
  		.setup_func = setup_subd_ao,
  		.sizeof_priv = sizeof(struct ni_670x_subd_priv),
  	},
  	{
  		.setup_func = setup_subd_dio,
  		.sizeof_priv = sizeof(struct ni_670x_subd_priv),
  	},
  };
  
  static const struct ni_670x_board ni_670x_boards[] = {
  	{
  		.device_id = 0x2c90,
  		.name = "PCI-6703",
  		.ao_chans = 16,
  		.ao_bits = 16,
  	},
  	{
  		.device_id = 0x1920,
  		.name = "PXI-6704",
  		.ao_chans = 32,
  		.ao_bits = 16,
  	},
  	{
  		.device_id = 0x1290,
  		.name = "PCI-6704",
  		.ao_chans = 32,
  		.ao_bits = 16,
  	 },
  };
  
  #define n_ni_670x_boards ((sizeof(ni_670x_boards)/sizeof(ni_670x_boards[0])))
  
  static const struct pci_device_id ni_670x_pci_table[] = {
  	{PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2c90)},
  	{PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1920)},
  	{0}
  };
  
  MODULE_DEVICE_TABLE(pci, ni_670x_pci_table);
  
  #define devpriv ((struct ni_670x_private *)dev->priv)
  
  static inline struct ni_670x_private *private(struct a4l_device *dev)
  {
  	return (struct ni_670x_private*) dev->priv;
  }
  
  
  static int ni_670x_attach (struct a4l_device *dev, a4l_lnkdesc_t *arg);
  static int ni_670x_detach(struct a4l_device *dev);
  
  static struct a4l_driver ni_670x_drv = {
  	.owner = THIS_MODULE,
  	.board_name = "analogy_ni_670x",
  	.driver_name = "ni_670x",
  	.attach = ni_670x_attach,
  	.detach = ni_670x_detach,
  	.privdata_size = sizeof(struct ni_670x_private),
  };
  
  static int __init driver_ni_670x_init_module(void)
  {
  	return a4l_register_drv (&ni_670x_drv);
  }
  
  static void __exit driver_ni_670x_cleanup_module(void)
  {
  	a4l_unregister_drv (&ni_670x_drv);
  }
  
  module_init(driver_ni_670x_init_module);
  module_exit(driver_ni_670x_cleanup_module);
  
  static int ni_670x_attach (struct a4l_device *dev, a4l_lnkdesc_t *arg)
  {
  	int ret, bus, slot, i, irq;
  	struct mite_struct *mite;
  	struct ni_670x_board* board = NULL;
  	int err;
  
  	if(arg->opts == NULL || arg->opts_size == 0)
  		bus = slot = 0;
  	else {
  		bus = arg->opts_size >= sizeof(unsigned long) ?
  			((unsigned long *)arg->opts)[0] : 0;
  		slot = arg->opts_size >= sizeof(unsigned long) * 2 ?
  			((unsigned long *)arg->opts)[1] : 0;
  	}
  
  	a4l_info(dev, "ni670x attach procedure started(bus=%d/slot=%d)...\n",
  		 bus, slot);
  
  	mite = NULL;
  
  	for(i = 0; i <  n_ni_670x_boards && mite == NULL; i++) {
  		mite = a4l_mite_find_device(bus,
  					    slot, ni_670x_boards[i].device_id);
  		board = (struct ni_670x_board*) &ni_670x_boards[i];
  	}
  
  	if(mite == NULL) {
  		a4l_err(dev, "%s: cannot find the MITE device\n", __FUNCTION__);
  		return -ENOENT;
  	}
  
  	a4l_info(dev, "Found device %d %s\n", i, ni_670x_boards[i].name);
  
  	devpriv->irq_polarity = PCIMIO_IRQ_POLARITY;
  	devpriv->irq_pin = 0;
  
  	devpriv->mite = mite;
  	devpriv->board_ptr = board;
  
  	ret = a4l_mite_setup(devpriv->mite, 0);
  	if (ret < 0) {
  		a4l_err(dev, "%s: error setting up mite\n", __FUNCTION__);
  		return ret;
  	}
  
  	irq = mite_irq(devpriv->mite);
  	devpriv->irq = irq;
  
  	a4l_info(dev, "found %s board\n", board->name);
  
  	for (i = 0; i < 2; i++) {
  		struct a4l_subdevice *subd =
  			a4l_alloc_subd(setup_subds[i].sizeof_priv, NULL);
  
  		if (subd == NULL) {
  			a4l_err(dev,
  				"%s: cannot allocate subdevice\n",
  				__FUNCTION__);
  			return -ENOMEM;
  		}
  
  		err = a4l_add_subd(dev, subd);
  		if (err != i) {
  			a4l_err(dev,
  				"%s: cannot add subdevice\n",
  				__FUNCTION__);
  			return err;
  		}
  
  		setup_subds[i].setup_func (subd);
  	}
  
  	/* Config of misc registers */
  	writel(0x10, devpriv->mite->daq_io_addr + MISC_CONTROL_OFFSET);
  	/* Config of ao registers */
  	writel(0x00, devpriv->mite->daq_io_addr + AO_CONTROL_OFFSET);
  
  	a4l_info(dev, "ni670x attached\n");
  
  	return 0;
  }
  
  static int ni_670x_detach(struct a4l_device *dev)
  {
  	a4l_info(dev, "ni670x detach procedure started...\n");
  
  	if(dev->priv != NULL && devpriv->mite != NULL)
  		a4l_mite_unsetup(devpriv->mite);
  
  	a4l_info(dev, "ni670x detach procedure succeeded...\n");
  
  	return 0;
  }
  
  
  static int ni_670x_dio_insn_config(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn)
  {
  	struct a4l_device *dev = subd->dev;
  	unsigned int *data = (unsigned int *)insn->data;
  	int chan = CR_CHAN(insn->chan_desc);
  	struct ni_670x_subd_priv *subdpriv =
  		(struct ni_670x_subd_priv *)subd->priv;
  
  	switch (data[0]) {
  	case A4L_INSN_CONFIG_DIO_OUTPUT:
  		subdpriv->io_bits |= 1 << chan;
  		break;
  	case A4L_INSN_CONFIG_DIO_INPUT:
  		subdpriv->io_bits &= ~(1 << chan);
  		break;
  	case A4L_INSN_CONFIG_DIO_QUERY:
  		data[1] = (subdpriv->io_bits & (1 << chan)) ?
  			A4L_OUTPUT : A4L_INPUT;
  		return 0;
  		break;
  	default:
  		return -EINVAL;
  		break;
  	}
  
  	writel(subdpriv->io_bits,
  	       devpriv->mite->daq_io_addr + DIO_PORT0_DIR_OFFSET);
  
  	return 0;
  }
  
  static int ni_670x_ao_winsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn)
  {
  	int i;
  	unsigned int tmp;
  	unsigned int* dtmp;
  	int chan;
  	dtmp = (unsigned int*)insn->data;
  	chan = CR_CHAN(insn->chan_desc);
  
  	/* Channel number mapping :
  
  	   NI 6703/ NI 6704     | NI 6704 Only
  	   ----------------------------------------------------
  	   vch(0)       :       0       | ich(16)       :       1
  	   vch(1)       :       2       | ich(17)       :       3
  	   .    :       .       |   .                   .
  	   .    :       .       |   .                   .
  	   .    :       .       |   .                   .
  	   vch(15)      :       30      | ich(31)       :       31 */
  
  	for (i = 0; i < insn->data_size / sizeof(unsigned int); i++) {
  
  		tmp = dtmp[i];
  
  		/* First write in channel register which channel to use */
  		writel(((chan & 15) << 1) | ((chan & 16) >> 4),
  		       private (subd->dev)->mite->daq_io_addr + AO_CHAN_OFFSET);
  
  		/* write channel value */
  		writel(dtmp[i],
  		       private(subd->dev)->mite->daq_io_addr + AO_VALUE_OFFSET);
  		private(subd->dev)->ao_readback[chan] = tmp;
  	}
  
     return 0;
  }
  
  static int ni_670x_ao_rinsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn)
  {
  	int i;
  	unsigned int* dtmp;
  	int chan = CR_CHAN(insn->chan_desc);
  
  	dtmp = (unsigned int*)insn->data;
  
  	for (i = 0; i < insn->data_size / sizeof(unsigned int); i++)
  		dtmp[i] = private(subd->dev)->ao_readback[chan];
  
  	return 0;
  }
  
  
  static int ni_670x_dio_insn_bits(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn)
  {
  	return -ENOSYS;
  }
  
  MODULE_DESCRIPTION("Analogy driver for NI670x series cards");
  MODULE_LICENSE("GPL");
  