浏览代码

Staging: Add the Meilhaus ME-IDS driver package

Originally written by Guenter Gebhardt <g.gebhardt@meilhaus.de>
and Krzysztof Gantzke <k.gantzke@meilhaus.de>

This is the drv/lnx/mod directory of ME-IDS 1.2.9 tarball with
some files from drv/lnx/include.

Signed-off-by: David Kiliani <mail@davidkiliani.de>
Cc: Guenter Gebhardt <g.gebhardt@meilhaus.de>
Cc: Krzysztof Gantzke <k.gantzke@meilhaus.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
David Kiliani 16 年之前
父节点
当前提交
3fedd14818
共有 100 个文件被更改,包括 31268 次插入0 次删除
  1. 2 0
      drivers/staging/Kconfig
  2. 1 0
      drivers/staging/Makefile
  3. 127 0
      drivers/staging/meilhaus/Kconfig
  4. 43 0
      drivers/staging/meilhaus/Makefile
  5. 10 0
      drivers/staging/meilhaus/TODO
  6. 215 0
      drivers/staging/meilhaus/me0600_device.c
  7. 97 0
      drivers/staging/meilhaus/me0600_device.h
  8. 415 0
      drivers/staging/meilhaus/me0600_dio.c
  9. 68 0
      drivers/staging/meilhaus/me0600_dio.h
  10. 41 0
      drivers/staging/meilhaus/me0600_dio_reg.h
  11. 478 0
      drivers/staging/meilhaus/me0600_ext_irq.c
  12. 58 0
      drivers/staging/meilhaus/me0600_ext_irq.h
  13. 18 0
      drivers/staging/meilhaus/me0600_ext_irq_reg.h
  14. 243 0
      drivers/staging/meilhaus/me0600_optoi.c
  15. 58 0
      drivers/staging/meilhaus/me0600_optoi.h
  16. 35 0
      drivers/staging/meilhaus/me0600_optoi_reg.h
  17. 359 0
      drivers/staging/meilhaus/me0600_relay.c
  18. 63 0
      drivers/staging/meilhaus/me0600_relay.h
  19. 36 0
      drivers/staging/meilhaus/me0600_relay_reg.h
  20. 238 0
      drivers/staging/meilhaus/me0600_ttli.c
  21. 58 0
      drivers/staging/meilhaus/me0600_ttli.h
  22. 35 0
      drivers/staging/meilhaus/me0600_ttli_reg.h
  23. 180 0
      drivers/staging/meilhaus/me0900_device.c
  24. 92 0
      drivers/staging/meilhaus/me0900_device.h
  25. 246 0
      drivers/staging/meilhaus/me0900_di.c
  26. 65 0
      drivers/staging/meilhaus/me0900_di.h
  27. 314 0
      drivers/staging/meilhaus/me0900_do.c
  28. 68 0
      drivers/staging/meilhaus/me0900_do.h
  29. 40 0
      drivers/staging/meilhaus/me0900_reg.h
  30. 208 0
      drivers/staging/meilhaus/me1000_device.c
  31. 59 0
      drivers/staging/meilhaus/me1000_device.h
  32. 438 0
      drivers/staging/meilhaus/me1000_dio.c
  33. 71 0
      drivers/staging/meilhaus/me1000_dio.h
  34. 50 0
      drivers/staging/meilhaus/me1000_dio_reg.h
  35. 256 0
      drivers/staging/meilhaus/me1400_device.c
  36. 108 0
      drivers/staging/meilhaus/me1400_device.h
  37. 517 0
      drivers/staging/meilhaus/me1400_ext_irq.c
  38. 62 0
      drivers/staging/meilhaus/me1400_ext_irq.h
  39. 56 0
      drivers/staging/meilhaus/me1400_ext_irq_reg.h
  40. 1033 0
      drivers/staging/meilhaus/me1600_ao.c
  41. 132 0
      drivers/staging/meilhaus/me1600_ao.h
  42. 66 0
      drivers/staging/meilhaus/me1600_ao_reg.h
  43. 261 0
      drivers/staging/meilhaus/me1600_device.c
  44. 101 0
      drivers/staging/meilhaus/me1600_device.h
  45. 3434 0
      drivers/staging/meilhaus/me4600_ai.c
  46. 180 0
      drivers/staging/meilhaus/me4600_ai.h
  47. 107 0
      drivers/staging/meilhaus/me4600_ai_reg.h
  48. 6011 0
      drivers/staging/meilhaus/me4600_ao.c
  49. 263 0
      drivers/staging/meilhaus/me4600_ao.h
  50. 113 0
      drivers/staging/meilhaus/me4600_ao_reg.h
  51. 373 0
      drivers/staging/meilhaus/me4600_device.c
  52. 151 0
      drivers/staging/meilhaus/me4600_device.h
  53. 256 0
      drivers/staging/meilhaus/me4600_di.c
  54. 64 0
      drivers/staging/meilhaus/me4600_di.h
  55. 510 0
      drivers/staging/meilhaus/me4600_dio.c
  56. 69 0
      drivers/staging/meilhaus/me4600_dio.h
  57. 63 0
      drivers/staging/meilhaus/me4600_dio_reg.h
  58. 433 0
      drivers/staging/meilhaus/me4600_do.c
  59. 65 0
      drivers/staging/meilhaus/me4600_do.h
  60. 467 0
      drivers/staging/meilhaus/me4600_ext_irq.c
  61. 78 0
      drivers/staging/meilhaus/me4600_ext_irq.h
  62. 41 0
      drivers/staging/meilhaus/me4600_ext_irq_reg.h
  63. 46 0
      drivers/staging/meilhaus/me4600_reg.h
  64. 3739 0
      drivers/staging/meilhaus/me6000_ao.c
  65. 200 0
      drivers/staging/meilhaus/me6000_ao.h
  66. 177 0
      drivers/staging/meilhaus/me6000_ao_reg.h
  67. 211 0
      drivers/staging/meilhaus/me6000_device.c
  68. 149 0
      drivers/staging/meilhaus/me6000_device.h
  69. 415 0
      drivers/staging/meilhaus/me6000_dio.c
  70. 68 0
      drivers/staging/meilhaus/me6000_dio.h
  71. 43 0
      drivers/staging/meilhaus/me6000_dio_reg.h
  72. 35 0
      drivers/staging/meilhaus/me6000_reg.h
  73. 187 0
      drivers/staging/meilhaus/me8100_device.c
  74. 97 0
      drivers/staging/meilhaus/me8100_device.h
  75. 693 0
      drivers/staging/meilhaus/me8100_di.c
  76. 89 0
      drivers/staging/meilhaus/me8100_di.h
  77. 47 0
      drivers/staging/meilhaus/me8100_di_reg.h
  78. 391 0
      drivers/staging/meilhaus/me8100_do.c
  79. 70 0
      drivers/staging/meilhaus/me8100_do.h
  80. 36 0
      drivers/staging/meilhaus/me8100_do_reg.h
  81. 41 0
      drivers/staging/meilhaus/me8100_reg.h
  82. 194 0
      drivers/staging/meilhaus/me8200_device.c
  83. 97 0
      drivers/staging/meilhaus/me8200_device.h
  84. 857 0
      drivers/staging/meilhaus/me8200_di.c
  85. 92 0
      drivers/staging/meilhaus/me8200_di.h
  86. 75 0
      drivers/staging/meilhaus/me8200_di_reg.h
  87. 418 0
      drivers/staging/meilhaus/me8200_dio.c
  88. 68 0
      drivers/staging/meilhaus/me8200_dio.h
  89. 43 0
      drivers/staging/meilhaus/me8200_dio_reg.h
  90. 600 0
      drivers/staging/meilhaus/me8200_do.c
  91. 75 0
      drivers/staging/meilhaus/me8200_do.h
  92. 40 0
      drivers/staging/meilhaus/me8200_do_reg.h
  93. 46 0
      drivers/staging/meilhaus/me8200_reg.h
  94. 1176 0
      drivers/staging/meilhaus/me8254.c
  95. 80 0
      drivers/staging/meilhaus/me8254.h
  96. 172 0
      drivers/staging/meilhaus/me8254_reg.h
  97. 462 0
      drivers/staging/meilhaus/me8255.c
  98. 59 0
      drivers/staging/meilhaus/me8255.h
  99. 50 0
      drivers/staging/meilhaus/me8255_reg.h
  100. 131 0
      drivers/staging/meilhaus/mecirc_buf.h

+ 2 - 0
drivers/staging/Kconfig

@@ -49,6 +49,8 @@ source "drivers/staging/sxg/Kconfig"
 
 source "drivers/staging/me4000/Kconfig"
 
+source "drivers/staging/meilhaus/Kconfig"
+
 source "drivers/staging/go7007/Kconfig"
 
 source "drivers/staging/usbip/Kconfig"

+ 1 - 0
drivers/staging/Makefile

@@ -7,6 +7,7 @@ obj-$(CONFIG_ET131X)		+= et131x/
 obj-$(CONFIG_SLICOSS)		+= slicoss/
 obj-$(CONFIG_SXG)		+= sxg/
 obj-$(CONFIG_ME4000)		+= me4000/
+obj-$(CONFIG_MEILHAUS)		+= meilhaus/
 obj-$(CONFIG_VIDEO_GO7007)	+= go7007/
 obj-$(CONFIG_USB_IP_COMMON)	+= usbip/
 obj-$(CONFIG_W35UND)		+= winbond/

+ 127 - 0
drivers/staging/meilhaus/Kconfig

@@ -0,0 +1,127 @@
+#
+# Meilhaus configuration
+#
+
+menuconfig MEILHAUS
+	tristate "Meilhaus support"
+	---help---
+	  If you have a Meilhaus card, say Y (or M) here.
+
+	  You need both this driver, and the driver for the particular
+	  data collection card.
+
+	  To compile this driver as a module, choose M here. The module will
+	  be called memain.
+
+if MEILHAUS
+
+config ME0600
+	tristate "Meilhaus ME-600 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-600 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me0600.
+
+config ME0900
+	tristate "Meilhaus ME-900 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-900 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me0900.
+
+config ME1000
+	tristate "Meilhaus ME-1000 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-1000 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me1000.
+
+config ME1400
+	tristate "Meilhaus ME-1400 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-1400 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me1400.
+
+config ME1600
+	tristate "Meilhaus ME-1600 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-1600 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me1600.
+
+config ME4600
+	tristate "Meilhaus ME-4600 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-4600 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me4600.
+
+config ME6000
+	tristate "Meilhaus ME-6000 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-6000 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me6000.
+
+config ME8100
+	tristate "Meilhaus ME-8100 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-8100 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me8100.
+
+config ME8200
+	tristate "Meilhaus ME-8200 support"
+	default n
+	depends on PCI
+	help
+	  This driver supports the Meilhaus ME-8200 family of boards
+	  that do data collection and multipurpose I/O.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called me8200.
+
+config MEDUMMY
+	tristate "Meilhaus dummy driver"
+	default n
+	depends on PCI
+	help
+	  This provides a dummy driver for the Meilhaus driver package
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called medummy.
+
+endif # MEILHAUS

+ 43 - 0
drivers/staging/meilhaus/Makefile

@@ -0,0 +1,43 @@
+#
+# Makefile for Meilhaus linux driver system
+#
+
+obj-$(CONFIG_MEILHAUS) += memain.o
+obj-$(CONFIG_ME1600) += me1600.o
+obj-$(CONFIG_ME1000) += me1000.o
+obj-$(CONFIG_ME1400) += me1400.o
+obj-$(CONFIG_ME4600) += me4600.o
+obj-$(CONFIG_ME6000) += me6000.o
+obj-$(CONFIG_ME0600) += me0600.o
+obj-$(CONFIG_ME8100) += me8100.o
+obj-$(CONFIG_ME8200) += me8200.o
+obj-$(CONFIG_ME0900) += me0900.o
+obj-$(CONFIG_MEDUMMY) += medummy.o
+
+
+me1600-objs := medevice.o medlist.o medlock.o me1600_device.o
+me1600-objs += mesubdevice.o meslist.o meslock.o me1600_ao.o
+
+me1000-objs := medevice.o medlist.o medlock.o me1000_device.o
+me1000-objs += mesubdevice.o meslist.o meslock.o me1000_dio.o
+
+me1400-objs := medevice.o medlist.o medlock.o me1400_device.o
+me1400-objs += mesubdevice.o meslist.o meslock.o me8254.o me8255.o me1400_ext_irq.o
+
+me4600-objs := medevice.o medlist.o medlock.o mefirmware.o me4600_device.o
+me4600-objs += mesubdevice.o meslist.o meslock.o me4600_do.o me4600_di.o me4600_dio.o me8254.o me4600_ai.o me4600_ao.o me4600_ext_irq.o
+
+me6000-objs := medevice.o medlist.o medlock.o mefirmware.o me6000_device.o
+me6000-objs += mesubdevice.o meslist.o meslock.o me6000_dio.o me6000_ao.o
+
+me0600-objs := medevice.o medlist.o medlock.o me0600_device.o
+me0600-objs += mesubdevice.o meslist.o meslock.o me0600_relay.o me0600_ttli.o me0600_optoi.o me0600_dio.o me0600_ext_irq.o
+
+me8100-objs := medevice.o medlist.o medlock.o me8100_device.o
+me8100-objs += mesubdevice.o meslist.o meslock.o me8100_di.o me8100_do.o me8254.o
+
+me8200-objs := medevice.o medlist.o medlock.o me8200_device.o
+me8200-objs += mesubdevice.o meslist.o meslock.o me8200_di.o me8200_do.o me8200_dio.o
+
+me0900-objs := medevice.o medlist.o medlock.o me0900_device.o
+me0900-objs += mesubdevice.o meslist.o meslock.o me0900_do.o me0900_di.o

+ 10 - 0
drivers/staging/meilhaus/TODO

@@ -0,0 +1,10 @@
+TODO:
+	- checkpatch.pl cleanups
+	- sparse issues
+	- Lindent
+	- audit userspace interface
+	- handle firmware properly
+	- possible comedi merge
+
+Please send cleanup patches to Greg Kroah-Hartman <greg@kroah.com>
+and CC: David Kiliani <mail@davidkiliani.de>

+ 215 - 0
drivers/staging/meilhaus/me0600_device.c

@@ -0,0 +1,215 @@
+/**
+ * @file me0600_device.c
+ *
+ * @brief ME-630 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "me0600_device.h"
+#include "mesubdevice.h"
+#include "me0600_relay.h"
+#include "me0600_ttli.h"
+#include "me0600_optoi.h"
+#include "me0600_dio.h"
+#include "me0600_ext_irq.h"
+
+me_device_t *me0600_pci_constructor(struct pci_dev *pci_device)
+{
+	me0600_device_t *me0600_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me0600_device = kmalloc(sizeof(me0600_device_t), GFP_KERNEL);
+
+	if (!me0600_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me0600_device, 0, sizeof(me0600_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me0600_device, pci_device);
+
+	if (err) {
+		kfree(me0600_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me0600_versions_get_device_index(me0600_device->base.info.pci.
+					     device_id);
+
+	// Initialize spin lock .
+	spin_lock_init(&me0600_device->dio_ctrl_reg_lock);
+	spin_lock_init(&me0600_device->intcsr_lock);
+
+	// Create subdevice instances.
+
+	for (i = 0; i < me0600_versions[version_idx].optoi_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0600_optoi_constructor(me0600_device->
+								base.info.pci.
+								reg_bases[2]);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0600_device);
+			kfree(me0600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0600_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me0600_versions[version_idx].relay_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0600_relay_constructor(me0600_device->
+								base.info.pci.
+								reg_bases[2]);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0600_device);
+			kfree(me0600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0600_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me0600_versions[version_idx].ttli_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0600_ttli_constructor(me0600_device->
+							       base.info.pci.
+							       reg_bases[2]);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0600_device);
+			kfree(me0600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0600_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me0600_versions[version_idx].dio_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0600_dio_constructor(me0600_device->
+							      base.info.pci.
+							      reg_bases[2], i,
+							      &me0600_device->
+							      dio_ctrl_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0600_device);
+			kfree(me0600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0600_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me0600_versions[version_idx].ext_irq_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *)
+		    me0600_ext_irq_constructor(me0600_device->base.info.pci.
+					       reg_bases[1],
+					       me0600_device->base.info.pci.
+					       reg_bases[2],
+					       &me0600_device->intcsr_lock, i,
+					       me0600_device->base.irq);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0600_device);
+			kfree(me0600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0600_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me0600_device;
+}
+
+// Init and exit of module.
+
+static int __init me0600_init(void)
+{
+	PDEBUG("executed.\n");
+	return 0;
+}
+
+static void __exit me0600_exit(void)
+{
+	PDEBUG("executed.\n");
+}
+
+module_init(me0600_init);
+
+module_exit(me0600_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for ME-6xx Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-6xx Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me0600_pci_constructor);

+ 97 - 0
drivers/staging/meilhaus/me0600_device.h

@@ -0,0 +1,97 @@
+/**
+ * @file me0600_device.h
+ *
+ * @brief ME-630 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_DEVICE_H
+#define _ME0600_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-630 device capabilities.
+ */
+typedef struct me0600_version {
+	uint16_t device_id;
+	unsigned int relay_subdevices;
+	unsigned int ttli_subdevices;
+	unsigned int optoi_subdevices;
+	unsigned int dio_subdevices;
+	unsigned int ext_irq_subdevices;
+} me0600_version_t;
+
+/**
+ * @brief Device capabilities.
+ */
+static me0600_version_t me0600_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME0630, 1, 1, 1, 2, 2},
+	{0},
+};
+
+#define ME0600_DEVICE_VERSIONS (sizeof(me0600_versions) / sizeof(me0600_version_t) - 1)	/**< Returns the number of entries in #me0600_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me0600_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me0600_versions.
+ */
+static inline unsigned int me0600_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME0600_DEVICE_VERSIONS; i++)
+		if (me0600_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-630 device class structure.
+ */
+typedef struct me0600_device {
+	me_device_t base;			/**< The Meilhaus device base class. */
+
+	/* Child class attributes. */
+	spinlock_t dio_ctrl_reg_lock;
+	spinlock_t intcsr_lock;
+} me0600_device_t;
+
+/**
+ * @brief The ME-630 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-630 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me0600_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 415 - 0
drivers/staging/meilhaus/me0600_dio.c

@@ -0,0 +1,415 @@
+/**
+ * @file me0600_dio.c
+ *
+ * @brief ME-630 digital input/output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me0600_dio_reg.h"
+#include "me0600_dio.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0600_dio_io_reset_subdevice(struct me_subdevice *subdevice,
+					 struct file *filep, int flags)
+{
+	me0600_dio_subdevice_t *instance;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_dio_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	mode &= ~(0x3 << (instance->dio_idx * 2));
+	outb(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outb(0x00, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0x00);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_dio_io_single_config(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int single_config,
+				       int ref,
+				       int trig_chan,
+				       int trig_type, int trig_edge, int flags)
+{
+	me0600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+	int size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+				mode &=
+				    ~((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+				      (instance->dio_idx * 2));
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				mode &=
+				    ~((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+				      (instance->dio_idx * 2));
+				mode |=
+				    ME0600_DIO_CONFIG_BIT_OUT_0 << (instance->
+								    dio_idx *
+								    2);
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outb(mode, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, mode);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_dio_io_single_read(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int *value, int time_out, int flags)
+{
+	me0600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+					     (instance->dio_idx * 2));
+
+			if ((mode ==
+			     (ME0600_DIO_CONFIG_BIT_OUT_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value =
+				    inb(instance->
+					port_reg) & (0x0001 << channel);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+					     (instance->dio_idx * 2));
+
+			if ((mode ==
+			     (ME0600_DIO_CONFIG_BIT_OUT_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value = inb(instance->port_reg) & 0x00FF;
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+
+		break;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_dio_io_single_write(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int value, int time_out, int flags)
+{
+	me0600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+	uint8_t byte;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME0600_DIO_CONFIG_BIT_OUT_0 <<
+			     (instance->dio_idx * 2))) {
+				byte = inb(instance->port_reg);
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outb(byte, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME0600_DIO_CONFIG_BIT_OUT_0) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME0600_DIO_CONFIG_BIT_OUT_0 <<
+			     (instance->dio_idx * 2))) {
+				outb(value, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+
+		break;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_dio_query_number_channels(me_subdevice_t * subdevice,
+					    int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_dio_query_subdevice_type(me_subdevice_t * subdevice,
+					   int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_dio_query_subdevice_caps(me_subdevice_t * subdevice,
+					   int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_DIR_BYTE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0600_dio_subdevice_t *me0600_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock)
+{
+	me0600_dio_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0600_dio_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0600_dio_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save digital i/o index */
+	subdevice->dio_idx = dio_idx;
+
+	/* Save the subdevice index */
+	subdevice->ctrl_reg = reg_base + ME0600_DIO_CONFIG_REG;
+	subdevice->port_reg = reg_base + ME0600_DIO_PORT_REG + dio_idx;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0600_dio_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0600_dio_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me0600_dio_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me0600_dio_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0600_dio_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0600_dio_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0600_dio_query_subdevice_caps;
+
+	return subdevice;
+}

+ 68 - 0
drivers/staging/meilhaus/me0600_dio.h

@@ -0,0 +1,68 @@
+/**
+ * @file me0600_dio.h
+ *
+ * @brief ME-630 digital input/output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_DIO_H_
+#define _ME0600_DIO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0600_dio_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+	unsigned int dio_idx;			/**< The index of the digital i/o on the device. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me0600_dio_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-630 digital input/ouput subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param dio_idx The index of the digital i/o port on the device.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0600_dio_subdevice_t *me0600_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 41 - 0
drivers/staging/meilhaus/me0600_dio_reg.h

@@ -0,0 +1,41 @@
+/**
+ * @file me0600_dio_reg.h
+ *
+ * @brief ME-630 digital input/output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_DIO_REG_H_
+#define _ME0600_DIO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0600_DIO_CONFIG_REG		0x0007
+#define ME0600_DIO_PORT_0_REG		0x0008
+#define ME0600_DIO_PORT_1_REG		0x0009
+#define ME0600_DIO_PORT_REG			ME0600_DIO_PORT_0_REG
+
+#define ME0600_DIO_CONFIG_BIT_OUT_0	0x0001
+#define ME0600_DIO_CONFIG_BIT_OUT_1	0x0004
+
+#endif
+#endif

+ 478 - 0
drivers/staging/meilhaus/me0600_ext_irq.c

@@ -0,0 +1,478 @@
+/**
+ * @file me0600_ext_irq.c
+ *
+ * @brief ME-630 external interrupt subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "meids.h"
+#include "medebug.h"
+
+#include "meplx_reg.h"
+#include "me0600_ext_irq_reg.h"
+#include "me0600_ext_irq.h"
+
+/*
+ * Functions
+ */
+
+static int me0600_ext_irq_io_irq_start(struct me_subdevice *subdevice,
+				       struct file *filep,
+				       int channel,
+				       int irq_source,
+				       int irq_edge, int irq_arg, int flags)
+{
+	me0600_ext_irq_subdevice_t *instance;
+	uint32_t tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags & ~ME_IO_IRQ_START_DIO_BIT) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (instance->lintno > 1) {
+		PERROR("Wrong idx=%d.\n", instance->lintno);
+		return ME_ERRNO_INVALID_SUBDEVICE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (irq_source != ME_IRQ_SOURCE_DIO_LINE) {
+		PERROR("Invalid irq source specified.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	if (irq_edge != ME_IRQ_EDGE_RISING) {
+		PERROR("Invalid irq edge specified.\n");
+		return ME_ERRNO_INVALID_IRQ_EDGE;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->intcsr_lock);
+	tmp = inl(instance->intcsr);
+	switch (instance->lintno) {
+	case 0:
+		tmp |=
+		    PLX_INTCSR_LOCAL_INT1_EN | PLX_INTCSR_LOCAL_INT1_POL |
+		    PLX_INTCSR_PCI_INT_EN;
+		break;
+	case 1:
+		tmp |=
+		    PLX_INTCSR_LOCAL_INT2_EN | PLX_INTCSR_LOCAL_INT2_POL |
+		    PLX_INTCSR_PCI_INT_EN;
+		break;
+	}
+	outl(tmp, instance->intcsr);
+	PDEBUG_REG("intcsr outl(plx:0x%X)=0x%x\n", instance->intcsr, tmp);
+	spin_unlock(instance->intcsr_lock);
+	instance->rised = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ext_irq_io_irq_wait(struct me_subdevice *subdevice,
+				      struct file *filep,
+				      int channel,
+				      int *irq_count,
+				      int *value, int time_out, int flags)
+{
+	me0600_ext_irq_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     (instance->rised !=
+							      0), t);
+
+			if (t == 0) {
+				PERROR("Wait on interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	instance->rised = 0;
+	*irq_count = instance->n;
+	*value = 1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_ext_irq_io_irq_stop(struct me_subdevice *subdevice,
+				      struct file *filep,
+				      int channel, int flags)
+{
+	me0600_ext_irq_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (instance->lintno > 1) {
+		PERROR("Wrong idx=%d.\n", instance->lintno);
+		return ME_ERRNO_INVALID_SUBDEVICE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->intcsr_lock);
+	tmp = inl(instance->intcsr);
+	switch (instance->lintno) {
+	case 0:
+		tmp &= ~PLX_INTCSR_LOCAL_INT1_EN;
+		break;
+	case 1:
+		tmp &= ~PLX_INTCSR_LOCAL_INT2_EN;
+		break;
+	}
+	outl(tmp, instance->intcsr);
+	PDEBUG_REG("intcsr outl(plx:0x%X)=0x%x\n", instance->intcsr, tmp);
+	spin_unlock(instance->intcsr_lock);
+	instance->rised = -1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_ext_irq_io_reset_subdevice(struct me_subdevice *subdevice,
+					     struct file *filep, int flags)
+{
+	me0600_ext_irq_subdevice_t *instance;
+	uint32_t tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->intcsr_lock);
+	tmp = inl(instance->intcsr);
+	switch (instance->lintno) {
+	case 0:
+		tmp |= PLX_INTCSR_LOCAL_INT1_POL | PLX_INTCSR_PCI_INT_EN;
+		tmp &= ~PLX_INTCSR_LOCAL_INT1_EN;
+		break;
+	case 1:
+		tmp |= PLX_INTCSR_LOCAL_INT2_POL | PLX_INTCSR_PCI_INT_EN;
+		tmp &= ~PLX_INTCSR_LOCAL_INT2_EN;
+		break;
+	}
+	outl(tmp, instance->intcsr);
+	PDEBUG_REG("intcsr outl(plx:0x%X)=0x%x\n", instance->intcsr, tmp);
+	spin_unlock(instance->intcsr_lock);
+
+	instance->rised = -1;
+	instance->n = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ext_irq_query_number_channels(struct me_subdevice *subdevice,
+						int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 1;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ext_irq_query_subdevice_type(struct me_subdevice *subdevice,
+					       int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_EXT_IRQ;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ext_irq_query_subdevice_caps(struct me_subdevice *subdevice,
+					       int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_EXT_IRQ_EDGE_RISING;
+	return ME_ERRNO_SUCCESS;
+}
+
+static void me0600_ext_irq_destructor(struct me_subdevice *subdevice)
+{
+	me0600_ext_irq_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ext_irq_subdevice_t *) subdevice;
+
+	free_irq(instance->irq, (void *)instance);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me0600_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me0600_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me0600_ext_irq_subdevice_t *instance;
+	uint32_t status;
+	uint32_t mask = PLX_INTCSR_PCI_INT_EN;
+	irqreturn_t ret = IRQ_HANDLED;
+
+	instance = (me0600_ext_irq_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	if (instance->lintno > 1) {
+		PERROR_CRITICAL
+		    ("%s():Wrong subdevice index=%d plx:irq_status_reg=0x%04X.\n",
+		     __FUNCTION__, instance->lintno, inl(instance->intcsr));
+		return IRQ_NONE;
+	}
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->intcsr_lock);
+	status = inl(instance->intcsr);
+	switch (instance->lintno) {
+	case 0:
+		mask |= PLX_INTCSR_LOCAL_INT1_STATE | PLX_INTCSR_LOCAL_INT1_EN;
+		break;
+	case 1:
+		mask |= PLX_INTCSR_LOCAL_INT2_STATE | PLX_INTCSR_LOCAL_INT2_EN;
+		break;
+	}
+
+	if ((status & mask) == mask) {
+		instance->rised = 1;
+		instance->n++;
+		inb(instance->reset_reg);
+		PDEBUG("Interrupt detected.\n");
+	} else {
+		PINFO
+		    ("%ld Shared interrupt. %s(): idx=0 plx:irq_status_reg=0x%04X\n",
+		     jiffies, __FUNCTION__, status);
+		ret = IRQ_NONE;
+	}
+	spin_unlock(instance->intcsr_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return ret;
+}
+
+me0600_ext_irq_subdevice_t *me0600_ext_irq_constructor(uint32_t plx_reg_base,
+						       uint32_t me0600_reg_base,
+						       spinlock_t * intcsr_lock,
+						       unsigned ext_irq_idx,
+						       int irq)
+{
+	me0600_ext_irq_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0600_ext_irq_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for 630_ext_irq instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0600_ext_irq_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->intcsr_lock = intcsr_lock;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	subdevice->lintno = ext_irq_idx;
+
+	/* Request interrupt line */
+	subdevice->irq = irq;
+
+	err = request_irq(subdevice->irq, me0600_isr,
+#ifdef IRQF_DISABLED
+			  IRQF_DISABLED | IRQF_SHARED,
+#else
+			  SA_INTERRUPT | SA_SHIRQ,
+#endif
+			  ME0600_NAME, (void *)subdevice);
+
+	if (err) {
+		PERROR("Cannot get interrupt line.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", subdevice->irq);
+
+	/* Initialize registers */
+	subdevice->intcsr = plx_reg_base + PLX_INTCSR;
+	subdevice->reset_reg =
+	    me0600_reg_base + ME0600_INT_0_RESET_REG + ext_irq_idx;
+
+	/* Initialize the subdevice methods */
+	subdevice->base.me_subdevice_io_irq_start = me0600_ext_irq_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me0600_ext_irq_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me0600_ext_irq_io_irq_stop;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0600_ext_irq_io_reset_subdevice;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0600_ext_irq_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0600_ext_irq_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0600_ext_irq_query_subdevice_caps;
+	subdevice->base.me_subdevice_destructor = me0600_ext_irq_destructor;
+
+	subdevice->rised = 0;
+	subdevice->n = 0;
+
+	return subdevice;
+}

+ 58 - 0
drivers/staging/meilhaus/me0600_ext_irq.h

@@ -0,0 +1,58 @@
+/**
+ * @file me0600_ext_irq.h
+ *
+ * @brief ME-630 external interrupt implementation.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME0600_EXT_IRQ_H_
+#define _ME0600_EXT_IRQ_H_
+
+#include <linux/sched.h>
+
+#include "mesubdevice.h"
+#include "meslock.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The ME-630 external interrupt subdevice class.
+ */
+typedef struct me0600_ext_irq_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *intcsr_lock;		/**< Spin lock to protect #intcsr. */
+
+	wait_queue_head_t wait_queue;		/**< Queue to put on threads waiting for an interrupt. */
+
+	int irq;				/**< The irq number assigned by PCI BIOS. */
+	int rised;				/**< If true an interrupt has occured. */
+	unsigned int n;				/**< The number of interrupt since the driver was loaded. */
+	unsigned int lintno;			/**< The number of the local PCI interrupt. */
+
+	uint32_t intcsr;			/**< The PLX interrupt control and status register. */
+	uint32_t reset_reg;			/**< The control register. */
+} me0600_ext_irq_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-630 external interrupt instance.
+ *
+ * @param plx_reg_base The register base address of the PLX chip as returned by the PCI BIOS.
+ * @param me0600_reg_base The register base address of the ME-630 device as returned by the PCI BIOS.
+ * @param irq The irq assigned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0600_ext_irq_subdevice_t *me0600_ext_irq_constructor(uint32_t plx_reg_base,
+						       uint32_t me0600_reg_base,
+						       spinlock_t * intcsr_lock,
+						       unsigned int ext_irq_idx,
+						       int irq);
+
+#endif
+#endif

+ 18 - 0
drivers/staging/meilhaus/me0600_ext_irq_reg.h

@@ -0,0 +1,18 @@
+/**
+ * @file me0600_ext_irq_reg.h
+ *
+ * @brief ME-630 external interrupt register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME0600_EXT_IRQ_REG_H_
+#define _ME0600_EXT_IRQ_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0600_INT_0_RESET_REG     0x0005
+#define ME0600_INT_1_RESET_REG     0x0006
+
+#endif
+#endif

+ 243 - 0
drivers/staging/meilhaus/me0600_optoi.c

@@ -0,0 +1,243 @@
+/**
+ * @file me0600_optoi.c
+ *
+ * @brief ME-630 Optoisolated input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me0600_optoi_reg.h"
+#include "me0600_optoi.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0600_optoi_io_reset_subdevice(struct me_subdevice *subdevice,
+					   struct file *filep, int flags)
+{
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	PDEBUG("executed.\n");
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_optoi_io_single_config(me_subdevice_t * subdevice,
+					 struct file *filep,
+					 int channel,
+					 int single_config,
+					 int ref,
+					 int trig_chan,
+					 int trig_type,
+					 int trig_edge, int flags)
+{
+	me0600_optoi_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_optoi_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config != ME_SINGLE_CONFIG_DIO_INPUT) {
+				PERROR("Invalid port direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+
+		break;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_optoi_io_single_read(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int *value, int time_out, int flags)
+{
+	me0600_optoi_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_optoi_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_optoi_query_number_channels(me_subdevice_t * subdevice,
+					      int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_optoi_query_subdevice_type(me_subdevice_t * subdevice,
+					     int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_optoi_query_subdevice_caps(me_subdevice_t * subdevice,
+					     int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0600_optoi_subdevice_t *me0600_optoi_constructor(uint32_t reg_base)
+{
+	me0600_optoi_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0600_optoi_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0600_optoi_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	/* Save the subdevice index */
+	subdevice->port_reg = reg_base + ME0600_OPTO_INPUT_REG;
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0600_optoi_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0600_optoi_io_single_config;
+	subdevice->base.me_subdevice_io_single_read =
+	    me0600_optoi_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0600_optoi_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0600_optoi_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0600_optoi_query_subdevice_caps;
+
+	return subdevice;
+}

+ 58 - 0
drivers/staging/meilhaus/me0600_optoi.h

@@ -0,0 +1,58 @@
+/**
+ * @file me0600_optoi.h
+ *
+ * @brief ME-630 Optoisolated input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_OPTOI_H_
+#define _ME0600_OPTOI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0600_optoi_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	uint32_t port_reg;			/**< Register holding the port status. */
+} me0600_optoi_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-630 Optoisolated input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0600_optoi_subdevice_t *me0600_optoi_constructor(uint32_t reg_base);
+
+#endif
+#endif

+ 35 - 0
drivers/staging/meilhaus/me0600_optoi_reg.h

@@ -0,0 +1,35 @@
+/**
+ * @file me0600_optoi_reg.h
+ *
+ * @brief ME-630 Optoisolated input subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_OPTOI_REG_H_
+#define _ME0600_OPTOI_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0600_OPTO_INPUT_REG      0x0004
+
+#endif
+#endif

+ 359 - 0
drivers/staging/meilhaus/me0600_relay.c

@@ -0,0 +1,359 @@
+/**
+ * @file me0600_relay.c
+ *
+ * @brief ME-630 relay subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me0600_relay_reg.h"
+#include "me0600_relay.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0600_relay_io_reset_subdevice(struct me_subdevice *subdevice,
+					   struct file *filep, int flags)
+{
+	me0600_relay_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_relay_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	outb(0x0, instance->port_0_reg);
+	PDEBUG_REG("port_0_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_0_reg - instance->reg_base, 0);
+	outb(0x0, instance->port_1_reg);
+	PDEBUG_REG("port_1_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_1_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_relay_io_single_config(me_subdevice_t * subdevice,
+					 struct file *filep,
+					 int channel,
+					 int single_config,
+					 int ref,
+					 int trig_chan,
+					 int trig_type,
+					 int trig_edge, int flags)
+{
+	me0600_relay_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_relay_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_WORD:
+		if (channel == 0) {
+			if (single_config != ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				PERROR("Invalid word direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+
+		break;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_relay_io_single_read(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int *value, int time_out, int flags)
+{
+	me0600_relay_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_relay_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_0_reg) & (0x1 << channel);
+		} else if ((channel >= 8) && (channel < 16)) {
+			*value =
+			    inb(instance->port_1_reg) & (0x1 << (channel - 8));
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_0_reg);
+		} else if (channel == 1) {
+			*value = inb(instance->port_1_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if (channel == 0) {
+			*value = (uint32_t) inb(instance->port_1_reg) << 8;
+			*value |= inb(instance->port_0_reg);
+		} else {
+			PERROR("Invalid word number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_relay_io_single_write(me_subdevice_t * subdevice,
+					struct file *filep,
+					int channel,
+					int value, int time_out, int flags)
+{
+	me0600_relay_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t state;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_relay_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			state = inb(instance->port_0_reg);
+			state =
+			    value ? (state | (0x1 << channel)) : (state &
+								  ~(0x1 <<
+								    channel));
+			outb(state, instance->port_0_reg);
+		} else if ((channel >= 8) && (channel < 16)) {
+			state = inb(instance->port_1_reg);
+			state =
+			    value ? (state | (0x1 << (channel - 8))) : (state &
+									~(0x1 <<
+									  (channel
+									   -
+									   8)));
+			outb(state, instance->port_1_reg);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			outb(value, instance->port_0_reg);
+		} else if (channel == 1) {
+			outb(value, instance->port_1_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if (channel == 0) {
+			outb(value, instance->port_0_reg);
+			outb(value >> 8, instance->port_1_reg);
+		} else {
+			PERROR("Invalid word number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+		break;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_relay_query_number_channels(me_subdevice_t * subdevice,
+					      int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 16;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_relay_query_subdevice_type(me_subdevice_t * subdevice,
+					     int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_relay_query_subdevice_caps(me_subdevice_t * subdevice,
+					     int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0600_relay_subdevice_t *me0600_relay_constructor(uint32_t reg_base)
+{
+	me0600_relay_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0600_relay_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0600_relay_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	/* Save the subdevice index */
+	subdevice->port_0_reg = reg_base + ME0600_RELAIS_0_REG;
+	subdevice->port_1_reg = reg_base + ME0600_RELAIS_1_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0600_relay_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0600_relay_io_single_config;
+	subdevice->base.me_subdevice_io_single_read =
+	    me0600_relay_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me0600_relay_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0600_relay_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0600_relay_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0600_relay_query_subdevice_caps;
+
+	return subdevice;
+}

+ 63 - 0
drivers/staging/meilhaus/me0600_relay.h

@@ -0,0 +1,63 @@
+/**
+ * @file me0600_relay.h
+ *
+ * @brief ME-630 relay subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_RELAY_H_
+#define _ME0600_RELAY_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0600_relay_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	unsigned long port_0_reg;			/**< Register holding the port status. */
+	unsigned long port_1_reg;			/**< Register holding the port status. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me0600_relay_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-630 relay subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0600_relay_subdevice_t *me0600_relay_constructor(uint32_t reg_base);
+
+#endif
+#endif

+ 36 - 0
drivers/staging/meilhaus/me0600_relay_reg.h

@@ -0,0 +1,36 @@
+/**
+ * @file me0600_relay_reg.h
+ *
+ * @brief ME-630 relay subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_RELAY_REG_H_
+#define _ME0600_RELAY_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0600_RELAIS_0_REG        0x0001
+#define ME0600_RELAIS_1_REG        0x0002
+
+#endif
+#endif

+ 238 - 0
drivers/staging/meilhaus/me0600_ttli.c

@@ -0,0 +1,238 @@
+/**
+ * @file me0600_ttli.c
+ *
+ * @brief ME-630 TTL input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me0600_ttli_reg.h"
+#include "me0600_ttli.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0600_ttli_io_reset_subdevice(struct me_subdevice *subdevice,
+					  struct file *filep, int flags)
+{
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	PDEBUG("executed.\n");
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ttli_io_single_config(me_subdevice_t * subdevice,
+					struct file *filep,
+					int channel,
+					int single_config,
+					int ref,
+					int trig_chan,
+					int trig_type, int trig_edge, int flags)
+{
+	me0600_ttli_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ttli_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config != ME_SINGLE_CONFIG_DIO_INPUT) {
+				PERROR("Invalid port direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+
+		break;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_ttli_io_single_read(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int *value, int time_out, int flags)
+{
+	me0600_ttli_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0600_ttli_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0600_ttli_query_number_channels(me_subdevice_t * subdevice,
+					     int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ttli_query_subdevice_type(me_subdevice_t * subdevice,
+					    int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0600_ttli_query_subdevice_caps(me_subdevice_t * subdevice,
+					    int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0600_ttli_subdevice_t *me0600_ttli_constructor(uint32_t reg_base)
+{
+	me0600_ttli_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0600_ttli_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0600_ttli_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	/* Save the subdevice index */
+	subdevice->port_reg = reg_base + ME0600_TTL_INPUT_REG;
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0600_ttli_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0600_ttli_io_single_config;
+	subdevice->base.me_subdevice_io_single_read =
+	    me0600_ttli_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0600_ttli_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0600_ttli_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0600_ttli_query_subdevice_caps;
+
+	return subdevice;
+}

+ 58 - 0
drivers/staging/meilhaus/me0600_ttli.h

@@ -0,0 +1,58 @@
+/**
+ * @file me0600_ttli.h
+ *
+ * @brief ME-630 TTL input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_TTLI_H_
+#define _ME0600_TTLI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0600_ttli_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	uint32_t port_reg;			/**< Register holding the port status. */
+} me0600_ttli_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-630 TTL input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0600_ttli_subdevice_t *me0600_ttli_constructor(uint32_t reg_base);
+
+#endif
+#endif

+ 35 - 0
drivers/staging/meilhaus/me0600_ttli_reg.h

@@ -0,0 +1,35 @@
+/**
+ * @file me0600_ttli_reg.h
+ *
+ * @brief ME-630 TTL input subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0600_TTLI_REG_H_
+#define _ME0600_TTLI_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0600_TTL_INPUT_REG       0x0003
+
+#endif
+#endif

+ 180 - 0
drivers/staging/meilhaus/me0900_device.c

@@ -0,0 +1,180 @@
+/**
+ * @file me0900_device.c
+ *
+ * @brief ME-9x device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+*/
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "me0900_device.h"
+#include "me0900_reg.h"
+#include "mesubdevice.h"
+#include "me0900_do.h"
+#include "me0900_di.h"
+
+me_device_t *me0900_pci_constructor(struct pci_dev *pci_device)
+{
+	me0900_device_t *me0900_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+	int port_shift;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me0900_device = kmalloc(sizeof(me0900_device_t), GFP_KERNEL);
+
+	if (!me0900_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me0900_device, 0, sizeof(me0900_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me0900_device, pci_device);
+
+	if (err) {
+		kfree(me0900_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me0900_versions_get_device_index(me0900_device->base.info.pci.
+					     device_id);
+
+	/* Initialize 8255 chip to desired mode */
+	if (me0900_device->base.info.pci.device_id ==
+	    PCI_DEVICE_ID_MEILHAUS_ME0940) {
+		outb(0x9B,
+		     me0900_device->base.info.pci.reg_bases[2] +
+		     ME0900_CTRL_REG);
+	} else if (me0900_device->base.info.pci.device_id ==
+		   PCI_DEVICE_ID_MEILHAUS_ME0950) {
+		outb(0x89,
+		     me0900_device->base.info.pci.reg_bases[2] +
+		     ME0900_CTRL_REG);
+		outb(0x00,
+		     me0900_device->base.info.pci.reg_bases[2] +
+		     ME0900_WRITE_ENABLE_REG);
+	} else if (me0900_device->base.info.pci.device_id ==
+		   PCI_DEVICE_ID_MEILHAUS_ME0960) {
+		outb(0x8B,
+		     me0900_device->base.info.pci.reg_bases[2] +
+		     ME0900_CTRL_REG);
+		outb(0x00,
+		     me0900_device->base.info.pci.reg_bases[2] +
+		     ME0900_WRITE_ENABLE_REG);
+	}
+
+	port_shift =
+	    (me0900_device->base.info.pci.device_id ==
+	     PCI_DEVICE_ID_MEILHAUS_ME0960) ? 1 : 0;
+	// Create subdevice instances.
+
+	for (i = 0; i < me0900_versions[version_idx].di_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0900_di_constructor(me0900_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     i + port_shift);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0900_device);
+			kfree(me0900_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0900_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me0900_versions[version_idx].do_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me0900_do_constructor(me0900_device->
+							     base.info.pci.
+							     reg_bases[2], i);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me0900_device);
+			kfree(me0900_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me0900_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me0900_device;
+}
+
+// Init and exit of module.
+
+static int __init me0900_init(void)
+{
+	PDEBUG("executed.\n.");
+	return 0;
+}
+
+static void __exit me0900_exit(void)
+{
+	PDEBUG("executed.\n.");
+}
+
+module_init(me0900_init);
+module_exit(me0900_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for ME-9x Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-9x Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me0900_pci_constructor);

+ 92 - 0
drivers/staging/meilhaus/me0900_device.h

@@ -0,0 +1,92 @@
+/**
+ * @file me0900_device.h
+ *
+ * @brief ME-0900 (ME-9x) device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0900_DEVICE_H
+#define _ME0900_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-0900 (ME-9x) device capabilities.
+ */
+typedef struct me0900_version {
+	uint16_t device_id;
+	unsigned int di_subdevices;
+	unsigned int do_subdevices;
+} me0900_version_t;
+
+/**
+ * @brief Device capabilities.
+ */
+static me0900_version_t me0900_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME0940, 2, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME0950, 0, 2},
+	{PCI_DEVICE_ID_MEILHAUS_ME0960, 1, 1},
+	{0},
+};
+
+#define ME0900_DEVICE_VERSIONS (sizeof(me0900_versions) / sizeof(me0900_version_t) - 1)	/**< Returns the number of entries in #me0900_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me0900_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me0900_versions.
+ */
+static inline unsigned int me0900_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME0900_DEVICE_VERSIONS; i++)
+		if (me0900_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-0900 (ME-9x) device class structure.
+ */
+typedef struct me0900_device {
+	me_device_t base;			/**< The Meilhaus device base class. */
+} me0900_device_t;
+
+/**
+ * @brief The ME-9x device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-0900 (ME-9x) device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me0900_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 246 - 0
drivers/staging/meilhaus/me0900_di.c

@@ -0,0 +1,246 @@
+/**
+ * @file me0900_di.c
+ *
+ * @brief ME-9x digital input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/version.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "meids.h"
+#include "medebug.h"
+#include "meplx_reg.h"
+#include "me0900_reg.h"
+#include "me0900_di.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0900_di_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	PDEBUG("executed.\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_di_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me0900_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+			} else {
+				PERROR("Invalid byte direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0900_di_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me0900_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = (~inb(instance->port_reg)) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = ~inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0900_di_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_di_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_di_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0900_di_subdevice_t *me0900_di_constructor(uint32_t reg_base,
+					     unsigned int di_idx)
+{
+	me0900_di_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0900_di_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0900_di_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	/* Save the subdevice index. */
+	subdevice->di_idx = di_idx;
+
+	/* Initialize registers */
+	if (di_idx == 0) {
+		subdevice->ctrl_reg = reg_base + ME0900_CTRL_REG;
+		subdevice->port_reg = reg_base + ME0900_PORT_A_REG;
+	} else {
+		subdevice->ctrl_reg = reg_base + ME0900_CTRL_REG;
+		subdevice->port_reg = reg_base + ME0900_PORT_B_REG;
+	}
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0900_di_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0900_di_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me0900_di_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0900_di_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0900_di_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0900_di_query_subdevice_caps;
+
+	return subdevice;
+}

+ 65 - 0
drivers/staging/meilhaus/me0900_di.h

@@ -0,0 +1,65 @@
+/**
+ * @file me0900_di.h
+ *
+ * @brief ME-9x digital input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0900_DI_H_
+#define _ME0900_DI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0900_di_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	unsigned int di_idx;
+
+	unsigned long ctrl_reg;
+	unsigned long port_reg;
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me0900_di_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-9x digital input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0900_di_subdevice_t *me0900_di_constructor(uint32_t me0900_reg_base,
+					     unsigned int di_idx);
+
+#endif
+#endif

+ 314 - 0
drivers/staging/meilhaus/me0900_do.c

@@ -0,0 +1,314 @@
+/**
+ * @file me0900_do.c
+ *
+ * @brief ME-9x digital output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me0900_reg.h"
+#include "me0900_do.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me0900_do_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me0900_do_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	outb(0xFF, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0xff);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_do_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me0900_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+			} else {
+				PERROR("Invalid byte direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0900_do_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me0900_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = (~inb(instance->port_reg)) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = ~inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0900_do_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me0900_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long state;
+
+	PDEBUG("executed.\n");
+
+	instance = (me0900_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			state = inb(instance->port_reg);
+			state =
+			    (!value) ? (state | (0x1 << channel)) : (state &
+								     ~(0x1 <<
+								       channel));
+			outb(state, instance->port_reg);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			outb(~(value), instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me0900_do_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_do_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me0900_do_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me0900_do_subdevice_t *me0900_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx)
+{
+	me0900_do_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me0900_do_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me0900_do_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	/* Save the subdevice index */
+	subdevice->do_idx = do_idx;
+
+	/* Initialize registers */
+	if (do_idx == 0) {
+		subdevice->ctrl_reg = reg_base + ME0900_CTRL_REG;
+		subdevice->port_reg = reg_base + ME0900_PORT_A_REG;
+		subdevice->enable_reg = reg_base + ME0900_WRITE_ENABLE_REG;
+		subdevice->disable_reg = reg_base + ME0900_WRITE_DISABLE_REG;
+	} else {
+		subdevice->ctrl_reg = reg_base + ME0900_CTRL_REG;
+		subdevice->port_reg = reg_base + ME0900_PORT_B_REG;
+		subdevice->enable_reg = reg_base + ME0900_WRITE_ENABLE_REG;
+		subdevice->disable_reg = reg_base + ME0900_WRITE_DISABLE_REG;
+	}
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me0900_do_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me0900_do_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me0900_do_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me0900_do_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me0900_do_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me0900_do_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me0900_do_query_subdevice_caps;
+
+	return subdevice;
+}

+ 68 - 0
drivers/staging/meilhaus/me0900_do.h

@@ -0,0 +1,68 @@
+/**
+ * @file me0900_do.h
+ *
+ * @brief ME-9x digital output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0900_DO_H_
+#define _ME0900_DO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me0900_do_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	unsigned int do_idx;
+
+	unsigned long ctrl_reg;
+	unsigned long port_reg;
+	unsigned long enable_reg;
+	unsigned long disable_reg;
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me0900_do_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-9x digital output subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param do_idx The index of the digital output subdevice on this device.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me0900_do_subdevice_t *me0900_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx);
+
+#endif
+#endif

+ 40 - 0
drivers/staging/meilhaus/me0900_reg.h

@@ -0,0 +1,40 @@
+/**
+ * @file me0900_reg.h
+ *
+ * @brief ME-9x register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME0900_REG_H_
+#define _ME0900_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME0900_PORT_A_REG          0x00
+#define ME0900_PORT_B_REG          0x01
+#define ME0900_PORT_C_REG          0x02
+#define ME0900_CTRL_REG            0x03	// ( ,w)
+#define ME0900_WRITE_ENABLE_REG    0x04	// (r,w)
+#define ME0900_WRITE_DISABLE_REG   0x08	// (r,w)
+
+#endif
+#endif

+ 208 - 0
drivers/staging/meilhaus/me1000_device.c

@@ -0,0 +1,208 @@
+/**
+ * @file me1000_device.c
+ *
+ * @brief ME-1000 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "me1000_device.h"
+#include "mesubdevice.h"
+#include "me1000_dio.h"
+
+static int me1000_config_load(me_device_t * me_device, struct file *filep,
+			      me_cfg_device_entry_t * config)
+{
+	me1000_device_t *me1000_device;
+	me1000_dio_subdevice_t *dio;
+
+	PDEBUG("executed.\n");
+
+	me1000_device = (me1000_device_t *) me_device;
+
+	if (config->count == 2) {
+		if (me_slist_get_number_subdevices(&me1000_device->base.slist)
+		    == 2) {
+			// Nothing to do.
+		} else {
+			// Remove 2 extra subdevices
+			dio =
+			    (me1000_dio_subdevice_t *)
+			    me_slist_del_subdevice_tail(&me1000_device->base.
+							slist);
+			if (dio)
+				dio->base.
+				    me_subdevice_destructor((me_subdevice_t *)
+							    dio);
+
+			dio =
+			    (me1000_dio_subdevice_t *)
+			    me_slist_del_subdevice_tail(&me1000_device->base.
+							slist);
+			if (dio)
+				dio->base.
+				    me_subdevice_destructor((me_subdevice_t *)
+							    dio);
+		}
+	} else if (config->count == 4) {
+		//Add 2 subdevices
+		if (me_slist_get_number_subdevices(&me1000_device->base.slist)
+		    == 2) {
+			dio =
+			    me1000_dio_constructor(me1000_device->base.info.pci.
+						   reg_bases[2], 2,
+						   &me1000_device->ctrl_lock);
+			if (!dio) {
+				PERROR("Cannot create dio subdevice.\n");
+				return ME_ERRNO_INTERNAL;
+			}
+			me_slist_add_subdevice_tail(&me1000_device->base.slist,
+						    (me_subdevice_t *) dio);
+
+			dio =
+			    me1000_dio_constructor(me1000_device->base.info.pci.
+						   reg_bases[2], 3,
+						   &me1000_device->ctrl_lock);
+			if (!dio) {
+				dio =
+				    (me1000_dio_subdevice_t *)
+				    me_slist_del_subdevice_tail(&me1000_device->
+								base.slist);
+				if (dio)
+					dio->base.
+					    me_subdevice_destructor((me_subdevice_t *) dio);
+
+				PERROR("Cannot create dio subdevice.\n");
+				return ME_ERRNO_INTERNAL;
+			}
+			me_slist_add_subdevice_tail(&me1000_device->base.slist,
+						    (me_subdevice_t *) dio);
+		} else {
+			// Nothing to do.
+		}
+	} else {
+		PERROR("Invalid configuration.\n");
+		return ME_ERRNO_INTERNAL;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+me_device_t *me1000_pci_constructor(struct pci_dev * pci_device)
+{
+	me1000_device_t *me1000_device;
+	me_subdevice_t *subdevice;
+	int err;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me1000_device = kmalloc(sizeof(me1000_device_t), GFP_KERNEL);
+
+	if (!me1000_device) {
+		PERROR("Cannot get memory for ME-1000 device instance.\n");
+		return NULL;
+	}
+
+	memset(me1000_device, 0, sizeof(me1000_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me1000_device, pci_device);
+
+	if (err) {
+		kfree(me1000_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+	// Initialize spin lock .
+	spin_lock_init(&me1000_device->ctrl_lock);
+
+	for (i = 0; i < 4; i++) {
+		subdevice =
+		    (me_subdevice_t *) me1000_dio_constructor(me1000_device->
+							      base.info.pci.
+							      reg_bases[2], i,
+							      &me1000_device->
+							      ctrl_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me1000_device);
+			kfree(me1000_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me1000_device->base.slist,
+					    subdevice);
+	}
+
+	// Overwrite base class methods.
+	me1000_device->base.me_device_config_load = me1000_config_load;
+
+	return (me_device_t *) me1000_device;
+}
+
+// Init and exit of module.
+static int __init me1000_init(void)
+{
+	PDEBUG("executed.\n");
+	return 0;
+}
+
+static void __exit me1000_exit(void)
+{
+	PDEBUG("executed.\n");
+}
+
+module_init(me1000_init);
+module_exit(me1000_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for Meilhaus ME-1000 Devices");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-1000 Digital I/O Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me1000_pci_constructor);

+ 59 - 0
drivers/staging/meilhaus/me1000_device.h

@@ -0,0 +1,59 @@
+/**
+ * @file me1000_device.h
+ *
+ * @brief ME-1000 device class instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1000_H_
+#define _ME1000_H_
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+#define ME1000_MAGIC_NUMBER	1000
+
+/**
+ * @brief The ME-1000 device class structure.
+ */
+typedef struct me1000_device {
+	me_device_t base;		/**< The Meilhaus device base class. */
+	spinlock_t ctrl_lock;		/**< Guards the DIO mode register. */
+} me1000_device_t;
+
+/**
+ * @brief The ME-1000 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-1000 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me1000_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 438 - 0
drivers/staging/meilhaus/me1000_dio.c

@@ -0,0 +1,438 @@
+/**
+ * @file me1000_dio.c
+ *
+ * @brief ME-1000 DIO subdevice instance.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "medebug.h"
+
+#include "me1000_dio_reg.h"
+#include "me1000_dio.h"
+
+/*
+ * Defines
+ */
+#define ME1000_DIO_MAGIC_NUMBER	0x1000	/**< The magic number of the class structure. */
+
+/*
+ * Functions
+ */
+
+static int me1000_dio_io_reset_subdevice(struct me_subdevice *subdevice,
+					 struct file *filep, int flags)
+{
+	me1000_dio_subdevice_t *instance;
+	uint32_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1000_dio_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	tmp = inl(instance->ctrl_reg);
+	tmp &= ~(0x1 << instance->dio_idx);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outl(0x00000000, instance->port_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1000_dio_io_single_config(struct me_subdevice *subdevice,
+				       struct file *filep,
+				       int channel,
+				       int single_config,
+				       int ref,
+				       int trig_chan,
+				       int trig_type, int trig_edge, int flags)
+{
+	me1000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	int ctrl;
+	int size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+
+	PDEBUG("executed.\n");
+
+	instance = (me1000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inl(instance->ctrl_reg);
+
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_DWORD:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+				ctrl &= ~(0x1 << instance->dio_idx);
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				ctrl |= 0x1 << instance->dio_idx;
+			} else {
+				PERROR("Invalid port direction.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1000_dio_io_single_read(struct me_subdevice *subdevice,
+				     struct file *filep,
+				     int channel,
+				     int *value, int time_out, int flags)
+{
+	me1000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 32)) {
+			*value = inl(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if ((channel >= 0) && (channel < 4)) {
+			*value =
+			    (inl(instance->port_reg) >> (channel * 8)) & 0xFF;
+		} else {
+			PERROR("Invalid byte number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if ((channel >= 0) && (channel < 2)) {
+			*value =
+			    (inl(instance->port_reg) >> (channel * 16)) &
+			    0xFFFF;
+		} else {
+			PERROR("Invalid word number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_DWORD:
+		if (channel == 0) {
+			*value = inl(instance->port_reg);
+		} else {
+			PERROR("Invalid dword number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1000_dio_io_single_write(struct me_subdevice *subdevice,
+				      struct file *filep,
+				      int channel,
+				      int value, int time_out, int flags)
+{
+	me1000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t config;
+	uint32_t state;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	config = inl(instance->ctrl_reg) & (0x1 << instance->dio_idx);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 32)) {
+			if (config) {
+				state = inl(instance->port_reg);
+				state =
+				    value ? (state | (0x1 << channel)) : (state
+									  &
+									  ~(0x1
+									    <<
+									    channel));
+				outl(state, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, state);
+			} else {
+				PERROR("Port is not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if ((channel >= 0) && (channel < 4)) {
+			if (config) {
+				state = inl(instance->port_reg);
+				state &= ~(0xFF << (channel * 8));
+				state |= (value & 0xFF) << (channel * 8);
+				outl(state, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, state);
+			} else {
+				PERROR("Port is not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if ((channel >= 0) && (channel < 2)) {
+			if (config) {
+				state = inl(instance->port_reg);
+				state &= ~(0xFFFF << (channel * 16));
+				state |= (value & 0xFFFF) << (channel * 16);
+				outl(state, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, state);
+			} else {
+				PERROR("Port is not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid word number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_DWORD:
+		if (channel == 0) {
+			if (config) {
+				outl(value, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, value);
+			} else {
+				PERROR("Port is not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid dword number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1000_dio_query_number_channels(struct me_subdevice *subdevice,
+					    int *number)
+{
+	PDEBUG("executed.\n");
+	*number = ME1000_DIO_NUMBER_CHANNELS;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1000_dio_query_subdevice_type(struct me_subdevice *subdevice,
+					   int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1000_dio_query_subdevice_caps(struct me_subdevice *subdevice,
+					   int *caps)
+{
+	me1000_dio_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1000_dio_subdevice_t *) subdevice;
+
+	*caps = ME_CAPS_DIO_DIR_DWORD;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+me1000_dio_subdevice_t *me1000_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock)
+{
+	me1000_dio_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me1000_dio_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for ME-1000 DIO instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me1000_dio_subdevice_t));
+
+	/* Check if counter index is out of range */
+
+	if (dio_idx >= ME1000_DIO_NUMBER_PORTS) {
+		PERROR("DIO index is out of range.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the DIO index */
+	subdevice->dio_idx = dio_idx;
+
+	/* Initialize registers. */
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+	subdevice->ctrl_reg = reg_base + ME1000_PORT_MODE;
+	subdevice->port_reg =
+	    reg_base + ME1000_PORT + (dio_idx * ME1000_PORT_STEP);
+
+	/* Override base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me1000_dio_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me1000_dio_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me1000_dio_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me1000_dio_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me1000_dio_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me1000_dio_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me1000_dio_query_subdevice_caps;
+
+	return subdevice;
+}

+ 71 - 0
drivers/staging/meilhaus/me1000_dio.h

@@ -0,0 +1,71 @@
+/**
+ * @file me1000_dio.h
+ *
+ * @brief Meilhaus ME-1000 digital i/o implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1000_DIO_H_
+#define _ME1000_DIO_H_
+
+#include "mesubdevice.h"
+#include "meslock.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The ME-1000 DIO subdevice class.
+ */
+typedef struct me1000_dio_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+//      uint32_t magic;                                 /**< The magic number unique for this structure. */
+
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg and #ctrl_reg_mirror from concurrent access. */
+	int dio_idx;					/**< The index of the DIO port on the device. */
+
+	unsigned long port_reg;			/**< Register to read or write a value from or to the port respectively. */
+	unsigned long ctrl_reg;			/**< Register to configure the DIO modes. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me1000_dio_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-1000 DIO instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param dio_idx The index of the DIO on the device.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the control register and from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me1000_dio_subdevice_t *me1000_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 50 - 0
drivers/staging/meilhaus/me1000_dio_reg.h

@@ -0,0 +1,50 @@
+/**
+ * @file me1000_dio_reg.h
+ *
+ * @brief ME-1000 digital i/o register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1000_DIO_REG_H_
+# define _ME1000_DIO_REG_H_
+
+# ifdef __KERNEL__
+
+# define ME1000_DIO_NUMBER_CHANNELS	32				/**< The number of channels per DIO port. */
+# define ME1000_DIO_NUMBER_PORTS	4				/**< The number of ports per ME-1000. */
+
+// # define ME1000_PORT_A                               0x0000                  /**< Port A base register offset. */
+// # define ME1000_PORT_B                               0x0004                  /**< Port B base register offset. */
+// # define ME1000_PORT_C                               0x0008                  /**< Port C base register offset. */
+// # define ME1000_PORT_D                               0x000C                  /**< Port D base register offset. */
+# define ME1000_PORT				0x0000			/**< Base for port's register. */
+# define ME1000_PORT_STEP			4				/**< Distance between port's register. */
+
+# define ME1000_PORT_MODE			0x0010			/**< Configuration register to switch the port direction. */
+// # define ME1000_PORT_MODE_OUTPUT_A   (1 << 0)                /**< If set, port A is in output, otherwise in input mode. */
+// # define ME1000_PORT_MODE_OUTPUT_B   (1 << 1)                /**< If set, port B is in output, otherwise in input mode. */
+// # define ME1000_PORT_MODE_OUTPUT_C   (1 << 2)                /**< If set, port C is in output, otherwise in input mode. */
+// # define ME1000_PORT_MODE_OUTPUT_D   (1 << 3)                /**< If set, port D is in output, otherwise in input mode. */
+
+# endif	//__KERNEL__
+#endif //_ME1000_DIO_REG_H_

+ 256 - 0
drivers/staging/meilhaus/me1400_device.c

@@ -0,0 +1,256 @@
+/**
+ * @file me1400_device.c
+ *
+ * @brief ME-1400 device instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+/*
+ * User application could also include the kernel header files. But the
+ * real kernel functions are protected by #ifdef __KERNEL__.
+ */
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * This must be defined before module.h is included. Not needed, when
+ * it is a built in driver.
+ */
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/version.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+
+#include "me1400_device.h"
+#include "me8254.h"
+#include "me8254_reg.h"
+#include "me8255.h"
+#include "me1400_ext_irq.h"
+
+me_device_t *me1400_pci_constructor(struct pci_dev *pci_device)
+{
+	int err;
+	me1400_device_t *me1400_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	unsigned int me8255_idx;
+	unsigned int dio_idx;
+	unsigned int me8254_idx;
+	unsigned int ctr_idx;
+	unsigned int ext_irq_idx;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me1400_device = kmalloc(sizeof(me1400_device_t), GFP_KERNEL);
+
+	if (!me1400_device) {
+		PERROR("Cannot get memory for 1400ate device instance.\n");
+		return NULL;
+	}
+
+	memset(me1400_device, 0, sizeof(me1400_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me1400_device, pci_device);
+
+	if (err) {
+		kfree(me1400_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Check for ME1400 extension device. If detected we fake a ME-1400 D device id. */
+	if (me1400_device->base.info.pci.device_id ==
+	    PCI_DEVICE_ID_MEILHAUS_ME140C) {
+		uint8_t ctrl;
+		ctrl =
+		    inb(me1400_device->base.info.pci.reg_bases[2] +
+			ME1400D_CLK_SRC_2_REG);
+		PDEBUG_REG("xxx_reg inb(0x%X+0x%X)=0x%x\n",
+			   me1400_device->base.info.pci.reg_bases[2],
+			   ME1400D_CLK_SRC_2_REG, ctrl);
+		outb(ctrl | 0xF0,
+		     me1400_device->base.info.pci.reg_bases[2] +
+		     ME1400D_CLK_SRC_2_REG);
+		PDEBUG_REG("xxx_reg outb(0x%X+0x%X)=0x%x\n",
+			   me1400_device->base.info.pci.reg_bases[2],
+			   ME1400D_CLK_SRC_2_REG, ctrl | 0xF0);
+		ctrl =
+		    inb(me1400_device->base.info.pci.reg_bases[2] +
+			ME1400D_CLK_SRC_2_REG);
+		PDEBUG_REG("xxx_reg inb(0x%X+0x%X)=0x%x\n",
+			   me1400_device->base.info.pci.reg_bases[2],
+			   ME1400D_CLK_SRC_2_REG, ctrl);
+
+		if ((ctrl & 0xF0) == 0xF0) {
+			PINFO("ME1400 D detected.\n");
+			me1400_device->base.info.pci.device_id =
+			    PCI_DEVICE_ID_MEILHAUS_ME140D;
+		}
+	}
+
+	/* Initialize global stuff of digital i/o subdevices. */
+	for (me8255_idx = 0; me8255_idx < ME1400_MAX_8255; me8255_idx++) {
+		me1400_device->dio_current_mode[me8255_idx] = 0;
+		spin_lock_init(&me1400_device->dio_ctrl_reg_lock[me8255_idx]);
+	}
+
+	/* Initialize global stuff of counter subdevices. */
+	spin_lock_init(&me1400_device->clk_src_reg_lock);
+
+	for (me8254_idx = 0; me8254_idx < ME1400_MAX_8254; me8254_idx++)
+		spin_lock_init(&me1400_device->ctr_ctrl_reg_lock[me8254_idx]);
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me1400_versions_get_device_index(me1400_device->base.info.pci.
+					     device_id);
+
+	/* Generate DIO subdevice instances. */
+	for (me8255_idx = 0;
+	     me8255_idx < me1400_versions[version_idx].dio_chips;
+	     me8255_idx++) {
+		for (dio_idx = 0; dio_idx < 3; dio_idx++) {
+			subdevice =
+			    (me_subdevice_t *)
+			    me8255_constructor(me1400_versions[version_idx].
+					       device_id,
+					       me1400_device->base.info.pci.
+					       reg_bases[2], me8255_idx,
+					       dio_idx,
+					       &me1400_device->
+					       dio_current_mode[me8255_idx],
+					       &me1400_device->
+					       dio_ctrl_reg_lock[me8255_idx]);
+
+			if (!subdevice) {
+				me_device_deinit((me_device_t *) me1400_device);
+				kfree(me1400_device);
+				PERROR("Cannot get memory for subdevice.\n");
+				return NULL;
+			}
+
+			me_slist_add_subdevice_tail(&me1400_device->base.slist,
+						    subdevice);
+		}
+	}
+
+	/* Generate counter subdevice instances. */
+	for (me8254_idx = 0;
+	     me8254_idx < me1400_versions[version_idx].ctr_chips;
+	     me8254_idx++) {
+		for (ctr_idx = 0; ctr_idx < 3; ctr_idx++) {
+			subdevice =
+			    (me_subdevice_t *)
+			    me8254_constructor(me1400_device->base.info.pci.
+					       device_id,
+					       me1400_device->base.info.pci.
+					       reg_bases[2], me8254_idx,
+					       ctr_idx,
+					       &me1400_device->
+					       ctr_ctrl_reg_lock[me8254_idx],
+					       &me1400_device->
+					       clk_src_reg_lock);
+
+			if (!subdevice) {
+				me_device_deinit((me_device_t *) me1400_device);
+				kfree(me1400_device);
+				PERROR("Cannot get memory for subdevice.\n");
+				return NULL;
+			}
+
+			me_slist_add_subdevice_tail(&me1400_device->base.slist,
+						    subdevice);
+		}
+	}
+
+	/* Generate external interrupt subdevice instances. */
+	for (ext_irq_idx = 0;
+	     ext_irq_idx < me1400_versions[version_idx].ext_irq_subdevices;
+	     ext_irq_idx++) {
+		subdevice =
+		    (me_subdevice_t *)
+		    me1400_ext_irq_constructor(me1400_device->base.info.pci.
+					       device_id,
+					       me1400_device->base.info.pci.
+					       reg_bases[1],
+					       me1400_device->base.info.pci.
+					       reg_bases[2],
+					       &me1400_device->clk_src_reg_lock,
+					       me1400_device->base.irq);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me1400_device);
+			kfree(me1400_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me1400_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me1400_device;
+}
+
+// Init and exit of module.
+
+static int __init me1400_init(void)
+{
+	PDEBUG("executed.\n");
+	return 0;
+}
+
+static void __exit me1400_exit(void)
+{
+	PDEBUG("executed.\n");
+}
+
+module_init(me1400_init);
+module_exit(me1400_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for Meilhaus ME-14xx devices");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-14xx MIO devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me1400_pci_constructor);

+ 108 - 0
drivers/staging/meilhaus/me1400_device.h

@@ -0,0 +1,108 @@
+/**
+ * @file me1400_device.c
+ *
+ * @brief ME-1400 device family instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1400_DEVICE_H_
+#define _ME1400_DEVICE_H_
+
+#include "metypes.h"
+#include "medefines.h"
+#include "meinternal.h"
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure to store device capabilities.
+ */
+typedef struct me1400_version {
+	uint16_t device_id;					/**< The PCI device id of the device. */
+	unsigned int dio_chips;				/**< The number of 8255 chips on the device. */
+	unsigned int ctr_chips;				/**< The number of 8254 chips on the device. */
+	unsigned int ext_irq_subdevices;	/**< The number of external interrupt inputs on the device. */
+} me1400_version_t;
+
+/**
+  * @brief Defines for each ME-1400 device version its capabilities.
+ */
+static me1400_version_t me1400_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME1400, 1, 0, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME140A, 1, 1, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME140B, 2, 2, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME14E0, 1, 0, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME14EA, 1, 1, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME14EB, 2, 2, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME140C, 1, 5, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME140D, 2, 10, 1},
+	{0}
+};
+
+#define ME1400_DEVICE_VERSIONS (sizeof(me1400_versions) / sizeof(me1400_version_t) - 1)	/**< Returns the number of entries in #me1400_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me1400_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me1400_versions.
+ */
+static inline unsigned int me1400_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME1400_DEVICE_VERSIONS; i++)
+		if (me1400_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+#define ME1400_MAX_8254		10	/**< The maximum number of 8254 counter subdevices available on any ME-1400 device. */
+#define ME1400_MAX_8255		2	/**< The maximum number of 8255 digital i/o subdevices available on any ME-1400 device. */
+
+/**
+ * @brief The ME-1400 device class.
+ */
+typedef struct me1400_device {
+	me_device_t base;									/**< The Meilhaus device base class. */
+
+	spinlock_t clk_src_reg_lock;						/**< Guards the 8254 clock source registers. */
+	spinlock_t ctr_ctrl_reg_lock[ME1400_MAX_8254];		/**< Guards the 8254 ctrl registers. */
+
+	int dio_current_mode[ME1400_MAX_8255];				/**< Saves the current mode setting of a single 8255 DIO chip. */
+	spinlock_t dio_ctrl_reg_lock[ME1400_MAX_8255];		/**< Guards the 8255 ctrl register and #dio_current_mode. */
+} me1400_device_t;
+
+/**
+ * @brief The ME-1400 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-1400 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me1400_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 517 - 0
drivers/staging/meilhaus/me1400_ext_irq.c

@@ -0,0 +1,517 @@
+/**
+ * @file me1400_ext_irq.c
+ *
+ * @brief ME-1400 external interrupt subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "medebug.h"
+#include "meids.h"
+
+#include "me1400_ext_irq.h"
+#include "me1400_ext_irq_reg.h"
+
+/*
+ * Defines
+ */
+#define ME1400_EXT_IRQ_MAGIC_NUMBER	0x1401	/**< The magic number of the class structure. */
+#define ME1400_EXT_IRQ_NUMBER_CHANNELS 1	/**< One channel per counter. */
+
+/*
+ * Functions
+ */
+
+static int me1400_ext_irq_io_irq_start(struct me_subdevice *subdevice,
+				       struct file *filep,
+				       int channel,
+				       int irq_source,
+				       int irq_edge, int irq_arg, int flags)
+{
+	me1400_ext_irq_subdevice_t *instance;
+	unsigned long cpu_flags;
+	uint8_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1400_ext_irq_subdevice_t *) subdevice;
+
+	if (flags & ~ME_IO_IRQ_START_DIO_BIT) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (irq_source != ME_IRQ_SOURCE_DIO_LINE) {
+		PERROR("Invalid irq source.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	if (irq_edge != ME_IRQ_EDGE_RISING) {
+		PERROR("Invalid irq edge.\n");
+		return ME_ERRNO_INVALID_IRQ_EDGE;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	spin_lock(instance->clk_src_reg_lock);
+//                      // Enable IRQ on PLX
+//                      tmp = inb(instance->plx_intcs_reg) | (PLX_LOCAL_INT1_EN | PLX_LOCAL_INT1_POL | PLX_PCI_INT_EN);
+//                      outb(tmp, instance->plx_intcs_reg);
+//                      PDEBUG_REG("ctrl_reg outb(PLX:0x%lX)=0x%x\n", instance->plx_intcs_reg, tmp);
+
+	// Enable IRQ
+	switch (instance->device_id) {
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		tmp = inb(instance->ctrl_reg);
+		tmp |= ME1400CD_EXT_IRQ_CLK_EN;
+		outb(tmp, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, tmp);
+		break;
+
+	default:
+		outb(ME1400AB_EXT_IRQ_IRQ_EN, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   ME1400AB_EXT_IRQ_IRQ_EN);
+		break;
+	}
+	spin_unlock(instance->clk_src_reg_lock);
+	instance->rised = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1400_ext_irq_io_irq_wait(struct me_subdevice *subdevice,
+				      struct file *filep,
+				      int channel,
+				      int *irq_count,
+				      int *value, int time_out, int flags)
+{
+	me1400_ext_irq_subdevice_t *instance;
+	unsigned long cpu_flags;
+	long t = 0;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1400_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time out.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		/* Convert to ticks */
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     (instance->rised !=
+							      0), t);
+
+			if (t == 0) {
+				PERROR("Wait on interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	instance->rised = 0;
+	*irq_count = instance->n;
+	*value = 1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1400_ext_irq_io_irq_stop(struct me_subdevice *subdevice,
+				      struct file *filep,
+				      int channel, int flags)
+{
+	me1400_ext_irq_subdevice_t *instance;
+	unsigned long cpu_flags;
+	uint8_t tmp;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1400_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->clk_src_reg_lock);
+//                      // Disable IRQ on PLX
+//                      tmp = inb(instance->plx_intcs_reg) & ( ~(PLX_LOCAL_INT1_EN | PLX_LOCAL_INT1_POL | PLX_PCI_INT_EN));
+//                      outb(tmp, instance->plx_intcs_reg);
+//                      PDEBUG_REG("ctrl_reg outb(PLX:0x%lX)=0x%x\n", instance->plx_intcs_reg, tmp);
+
+	switch (instance->device_id) {
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		tmp = inb(instance->ctrl_reg);
+		tmp &= ~ME1400CD_EXT_IRQ_CLK_EN;
+		outb(tmp, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, tmp);
+
+		break;
+
+	default:
+		outb(0x00, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, 0x00);
+		break;
+	}
+	spin_unlock(instance->clk_src_reg_lock);
+	instance->rised = -1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1400_ext_irq_io_reset_subdevice(struct me_subdevice *subdevice,
+					     struct file *filep, int flags)
+{
+	me1400_ext_irq_subdevice_t *instance =
+	    (me1400_ext_irq_subdevice_t *) subdevice;
+
+	PDEBUG("executed.\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	instance->n = 0;
+	return me1400_ext_irq_io_irq_stop(subdevice, filep, 0, flags);
+}
+
+static int me1400_ext_irq_query_number_channels(struct me_subdevice *subdevice,
+						int *number)
+{
+	PDEBUG("executed.\n");
+	*number = ME1400_EXT_IRQ_NUMBER_CHANNELS;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1400_ext_irq_query_subdevice_type(struct me_subdevice *subdevice,
+					       int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_EXT_IRQ;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1400_ext_irq_query_subdevice_caps(struct me_subdevice *subdevice,
+					       int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_EXT_IRQ_EDGE_RISING;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1400_ext_irq_query_subdevice_caps_args(struct me_subdevice
+						    *subdevice, int cap,
+						    int *args, int count)
+{
+	PDEBUG("executed.\n");
+	return ME_ERRNO_NOT_SUPPORTED;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me1400_ext_irq_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me1400_ext_irq_isr(int irq, void *dev_id,
+				      struct pt_regs *regs)
+#endif
+{
+	me1400_ext_irq_subdevice_t *instance;
+	uint32_t status;
+	uint8_t tmp;
+
+	instance = (me1400_ext_irq_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	spin_lock(&instance->subdevice_lock);
+	status = inl(instance->plx_intcs_reg);
+//              if (!((status & PLX_LOCAL_INT1_STATE) && (status & PLX_LOCAL_INT1_EN) && (status & PLX_PCI_INT_EN)))
+	if ((status &
+	     (PLX_LOCAL_INT1_STATE | PLX_LOCAL_INT1_EN | PLX_PCI_INT_EN)) !=
+	    (PLX_LOCAL_INT1_STATE | PLX_LOCAL_INT1_EN | PLX_PCI_INT_EN)) {
+		spin_unlock(&instance->subdevice_lock);
+		PINFO("%ld Shared interrupt. %s(): irq_status_reg=0x%04X\n",
+		      jiffies, __FUNCTION__, status);
+		return IRQ_NONE;
+	}
+
+	inl(instance->ctrl_reg);
+
+	PDEBUG("executed.\n");
+
+	instance->n++;
+	instance->rised = 1;
+
+	switch (instance->device_id) {
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		spin_lock(instance->clk_src_reg_lock);
+		tmp = inb(instance->ctrl_reg);
+		tmp &= ~ME1400CD_EXT_IRQ_CLK_EN;
+		outb(tmp, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, tmp);
+		tmp |= ME1400CD_EXT_IRQ_CLK_EN;
+		outb(tmp, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, tmp);
+		spin_unlock(instance->clk_src_reg_lock);
+
+		break;
+
+	default:
+		outb(0, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, 0);
+		outb(ME1400AB_EXT_IRQ_IRQ_EN, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   ME1400AB_EXT_IRQ_IRQ_EN);
+		break;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+static void me1400_ext_irq_destructor(struct me_subdevice *subdevice)
+{
+	me1400_ext_irq_subdevice_t *instance;
+	uint8_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1400_ext_irq_subdevice_t *) subdevice;
+
+	// Disable IRQ on PLX
+	tmp =
+	    inb(instance->
+		plx_intcs_reg) & (~(PLX_LOCAL_INT1_EN | PLX_LOCAL_INT1_POL |
+				    PLX_PCI_INT_EN));
+	outb(tmp, instance->plx_intcs_reg);
+	PDEBUG_REG("ctrl_reg outb(plx:0x%lX)=0x%x\n", instance->plx_intcs_reg,
+		   tmp);
+
+	free_irq(instance->irq, (void *)instance);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+me1400_ext_irq_subdevice_t *me1400_ext_irq_constructor(uint32_t device_id,
+						       uint32_t plx_reg_base,
+						       uint32_t me1400_reg_base,
+						       spinlock_t *
+						       clk_src_reg_lock,
+						       int irq)
+{
+	me1400_ext_irq_subdevice_t *subdevice;
+	int err;
+	uint8_t tmp;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me1400_ext_irq_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for 1400_ext_irq instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me1400_ext_irq_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->clk_src_reg_lock = clk_src_reg_lock;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	subdevice->irq = irq;
+
+	err = request_irq(irq, me1400_ext_irq_isr,
+#ifdef IRQF_DISABLED
+			  IRQF_DISABLED | IRQF_SHARED,
+#else
+			  SA_INTERRUPT | SA_SHIRQ,
+#endif
+			  ME1400_NAME, (void *)subdevice);
+
+	if (err) {
+		PERROR("Can't get irq.\n");
+		me_subdevice_deinit(&subdevice->base);
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", subdevice->irq);
+
+	/* Initialize registers */
+	subdevice->plx_intcs_reg = plx_reg_base + PLX_INTCSR_REG;
+	subdevice->ctrl_reg = me1400_reg_base + ME1400AB_EXT_IRQ_CTRL_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = me1400_reg_base;
+#endif
+
+	// Enable IRQ on PLX
+	tmp =
+	    inb(subdevice->
+		plx_intcs_reg) | (PLX_LOCAL_INT1_EN | PLX_LOCAL_INT1_POL |
+				  PLX_PCI_INT_EN);
+	outb(tmp, subdevice->plx_intcs_reg);
+	PDEBUG_REG("ctrl_reg outb(Pplx:0x%lX)=0x%x\n", subdevice->plx_intcs_reg,
+		   tmp);
+
+	/* Initialize the subdevice methods */
+	subdevice->base.me_subdevice_io_irq_start = me1400_ext_irq_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me1400_ext_irq_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me1400_ext_irq_io_irq_stop;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me1400_ext_irq_io_reset_subdevice;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me1400_ext_irq_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me1400_ext_irq_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me1400_ext_irq_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me1400_ext_irq_query_subdevice_caps_args;
+	subdevice->base.me_subdevice_destructor = me1400_ext_irq_destructor;
+
+	subdevice->rised = 0;
+	subdevice->n = 0;
+
+	return subdevice;
+}

+ 62 - 0
drivers/staging/meilhaus/me1400_ext_irq.h

@@ -0,0 +1,62 @@
+/**
+ * @file me1400_ext_irq.h
+ *
+ * @brief ME-1400 external interrupt implementation.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME1400_EXT_IRQ_H_
+#define _ME1400_EXT_IRQ_H_
+
+#include <linux/sched.h>
+
+#include "mesubdevice.h"
+#include "meslock.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The ME-1400 external interrupt subdevice class.
+ */
+typedef struct me1400_ext_irq_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *clk_src_reg_lock;	/**< Lock protecting the clock control register. */
+
+	wait_queue_head_t wait_queue;	/**< Queue to put on threads waiting for an interrupt. */
+
+	uint32_t device_id;				/**< The device id of the device holding the subdevice. */
+	int irq;						/**< The irq number assigned by PCI BIOS. */
+	int rised;						/**< If true an interrupt has occured. */
+	unsigned int n;					/**< The number of interrupt since the driver was loaded. */
+
+	unsigned long plx_intcs_reg;	/**< The PLX interrupt control and status register. */
+	unsigned long ctrl_reg;			/**< The control register. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me1400_ext_irq_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-1400 external interrupt instance.
+ *
+ * @param plx_reg_base The register base address of the PLX chip as returned by the PCI BIOS.
+ * @param me1400_reg_base The register base address of the ME-1400 device as returned by the PCI BIOS.
+ * @param irq The irq assigned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me1400_ext_irq_subdevice_t *me1400_ext_irq_constructor(uint32_t device_id,
+						       uint32_t plx_reg_base,
+						       uint32_t me1400_reg_base,
+						       spinlock_t *
+						       clk_src_reg_lock,
+						       int irq);
+
+#endif
+#endif

+ 56 - 0
drivers/staging/meilhaus/me1400_ext_irq_reg.h

@@ -0,0 +1,56 @@
+/**
+ * @file me1400_ext_irq_reg.h
+ *
+ * @brief ME-1400 external interrupt register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1400_EXT_IRQ_REG_H_
+# define _ME1400_EXT_IRQ_REG_H_
+
+# ifdef __KERNEL__
+
+#  define PLX_INTCSR_REG				0x4C	/**< The PLX interrupt control and status register offset. */
+#  define PLX_ICR_REG					0x50	/**< The PLX initialization control register offset. */
+
+#  define PLX_LOCAL_INT1_EN				0x01	/**< If set the local interrupt 1 is enabled. */
+#  define PLX_LOCAL_INT1_POL			0x02	/**< If set the local interrupt 1 polarity is high active. */
+#  define PLX_LOCAL_INT1_STATE			0x04	/**< If set the local interrupt 1 is activ. */
+#  define PLX_LOCAL_INT2_EN				0x08	/**< If set the local interrupt 2 is enabled. */
+#  define PLX_LOCAL_INT2_POL			0x10	/**< If set the local interrupt 2 polarity is high active. */
+#  define PLX_LOCAL_INT2_STATE			0x20	/**< If set the local interrupt 2 is activ. */
+#  define PLX_PCI_INT_EN				0x40	/**< If set the PCI interrupt is enabled. */
+#  define PLX_SOFT_INT					0x80	/**< If set an interrupt is generated. */
+
+#  define ME1400AB_EXT_IRQ_CTRL_REG		0x11	/**< The external interrupt control register offset. */
+
+#  define ME1400AB_EXT_IRQ_CLK_EN		0x01	/**< If this bit is set, the clock output is enabled. */
+#  define ME1400AB_EXT_IRQ_IRQ_EN		0x02	/**< If set the external interrupt is enabled. Clearing this bit clears a pending interrupt. */
+
+#  define ME1400CD_EXT_IRQ_CTRL_REG		0x11	/**< The external interrupt control register offset. */
+
+#  define ME1400CD_EXT_IRQ_CLK_EN		0x10	/**< If set the external interrupt is enabled. Clearing this bit clears a pending interrupt.*/
+
+# endif	//__KERNEL__
+
+#endif //_ME1400_EXT_IRQ_REG_H_

+ 1033 - 0
drivers/staging/meilhaus/me1600_ao.c

@@ -0,0 +1,1033 @@
+/**
+ * @file me1600_ao.c
+ *
+ * @brief ME-1600 analog output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/* Includes
+ */
+
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+
+#include <linux/workqueue.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "medebug.h"
+
+#include "me1600_ao_reg.h"
+#include "me1600_ao.h"
+
+/* Defines
+ */
+
+static void me1600_ao_destructor(struct me_subdevice *subdevice);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void me1600_ao_work_control_task(void *subdevice);
+#else
+static void me1600_ao_work_control_task(struct work_struct *work);
+#endif
+
+static int me1600_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags);
+static int me1600_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep, int channel,
+				      int single_config, int ref, int trig_chan,
+				      int trig_type, int trig_edge, int flags);
+static int me1600_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep, int channel, int *value,
+				    int time_out, int flags);
+static int me1600_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep, int channel, int value,
+				     int time_out, int flags);
+static int me1600_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number);
+static int me1600_ao_query_subdevice_type(me_subdevice_t * subdevice, int *type,
+					  int *subtype);
+static int me1600_ao_query_subdevice_caps(me_subdevice_t * subdevice,
+					  int *caps);
+static int me1600_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit, int *min, int *max,
+					    int *maxdata, int *range);
+static int me1600_ao_query_number_ranges(me_subdevice_t * subdevice, int unit,
+					 int *count);
+static int me1600_ao_query_range_info(me_subdevice_t * subdevice, int range,
+				      int *unit, int *min, int *max,
+				      int *maxdata);
+
+/* Functions
+ */
+
+me1600_ao_subdevice_t *me1600_ao_constructor(uint32_t reg_base,
+					     unsigned int ao_idx,
+					     int curr,
+					     spinlock_t * config_regs_lock,
+					     spinlock_t * ao_shadows_lock,
+					     me1600_ao_shadow_t *
+					     ao_regs_shadows,
+					     struct workqueue_struct *me1600_wq)
+{
+	me1600_ao_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed. idx=%d\n", ao_idx);
+
+	// Allocate memory for subdevice instance.
+	subdevice = kmalloc(sizeof(me1600_ao_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR
+		    ("Cannot get memory for analog output subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me1600_ao_subdevice_t));
+
+	// Initialize subdevice base class.
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->config_regs_lock = config_regs_lock;
+	subdevice->ao_shadows_lock = ao_shadows_lock;
+
+	// Save the subdevice index.
+	subdevice->ao_idx = ao_idx;
+
+	// Initialize range lists.
+	subdevice->u_ranges_count = 2;
+
+	subdevice->u_ranges[0].min = 0;	//0V
+	subdevice->u_ranges[0].max = 9997558;	//10V
+
+	subdevice->u_ranges[1].min = -10E6;	//-10V
+	subdevice->u_ranges[1].max = 9995117;	//10V
+
+	if (curr) {		// This is version with current outputs.
+		subdevice->i_ranges_count = 2;
+
+		subdevice->i_ranges[0].min = 0;	//0mA
+		subdevice->i_ranges[0].max = 19995117;	//20mA
+
+		subdevice->i_ranges[1].min = 4E3;	//4mA
+		subdevice->i_ranges[1].max = 19995118;	//20mA
+	} else {		// This is version without current outputs.
+		subdevice->i_ranges_count = 0;
+
+		subdevice->i_ranges[0].min = 0;	//0mA
+		subdevice->i_ranges[0].max = 0;	//0mA
+
+		subdevice->i_ranges[1].min = 0;	//0mA
+		subdevice->i_ranges[1].max = 0;	//0mA
+	}
+
+	// Initialize registers.
+	subdevice->uni_bi_reg = reg_base + ME1600_UNI_BI_REG;
+	subdevice->i_range_reg = reg_base + ME1600_020_420_REG;
+	subdevice->sim_output_reg = reg_base + ME1600_SIM_OUTPUT_REG;
+	subdevice->current_on_reg = reg_base + ME1600_CURRENT_ON_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	// Initialize shadow structure.
+	subdevice->ao_regs_shadows = ao_regs_shadows;
+
+	// Override base class methods.
+	subdevice->base.me_subdevice_destructor = me1600_ao_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me1600_ao_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me1600_ao_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me1600_ao_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me1600_ao_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me1600_ao_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me1600_ao_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me1600_ao_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_range_by_min_max =
+	    me1600_ao_query_range_by_min_max;
+	subdevice->base.me_subdevice_query_number_ranges =
+	    me1600_ao_query_number_ranges;
+	subdevice->base.me_subdevice_query_range_info =
+	    me1600_ao_query_range_info;
+
+	// Initialize wait queue.
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	// Prepare work queue.
+	subdevice->me1600_workqueue = me1600_wq;
+
+/* workqueue API changed in kernel 2.6.20 */
+#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) )
+	INIT_WORK(&subdevice->ao_control_task, me1600_ao_work_control_task,
+		  (void *)subdevice);
+#else
+	INIT_DELAYED_WORK(&subdevice->ao_control_task,
+			  me1600_ao_work_control_task);
+#endif
+	return subdevice;
+}
+
+static void me1600_ao_destructor(struct me_subdevice *subdevice)
+{
+	me1600_ao_subdevice_t *instance;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	instance->ao_control_task_flag = 0;
+
+	// Reset subdevice to asure clean exit.
+	me1600_ao_io_reset_subdevice(subdevice, NULL,
+				     ME_IO_RESET_SUBDEVICE_NO_FLAGS);
+
+	// Remove any tasks from work queue. This is paranoic because it was done allready in reset().
+	if (!cancel_delayed_work(&instance->ao_control_task)) {	//Wait 2 ticks to be sure that control task is removed from queue.
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(2);
+	}
+}
+
+static int me1600_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags)
+{
+	me1600_ao_subdevice_t *instance;
+	uint16_t tmp;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+	(instance->ao_regs_shadows)->trigger &= ~(0x1 << instance->ao_idx);	//Cancell waiting for trigger.
+
+	// Reset all settings.
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ao_shadows_lock);
+	(instance->ao_regs_shadows)->shadow[instance->ao_idx] = 0;
+	(instance->ao_regs_shadows)->mirror[instance->ao_idx] = 0;
+	(instance->ao_regs_shadows)->trigger &= ~(0x1 << instance->ao_idx);	//Not waiting for triggering.
+	(instance->ao_regs_shadows)->synchronous &= ~(0x1 << instance->ao_idx);	//Individual triggering.
+
+	// Set output to default (safe) state.
+	spin_lock(instance->config_regs_lock);
+	tmp = inw(instance->uni_bi_reg);	// unipolar
+	tmp |= (0x1 << instance->ao_idx);
+	outw(tmp, instance->uni_bi_reg);
+	PDEBUG_REG("uni_bi_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->uni_bi_reg - instance->reg_base, tmp);
+
+	tmp = inw(instance->current_on_reg);	// Volts only!
+	tmp &= ~(0x1 << instance->ao_idx);
+	tmp &= 0x00FF;
+	outw(tmp, instance->current_on_reg);
+	PDEBUG_REG("current_on_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->current_on_reg - instance->reg_base, tmp);
+
+	tmp = inw(instance->i_range_reg);	// 0..20mA <= If exists.
+	tmp &= ~(0x1 << instance->ao_idx);
+	outw(tmp, instance->i_range_reg);
+	PDEBUG_REG("i_range_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->i_range_reg - instance->reg_base, tmp);
+
+	outw(0, (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+	PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   (instance->ao_regs_shadows)->registry[instance->ao_idx] -
+		   instance->reg_base, 0);
+
+	// Trigger output.
+	outw(0x0000, instance->sim_output_reg);
+	PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->sim_output_reg - instance->reg_base, 0x0000);
+	outw(0xFFFF, instance->sim_output_reg);
+	PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->sim_output_reg - instance->reg_base, 0xFFFF);
+	spin_unlock(instance->config_regs_lock);
+	spin_unlock(instance->ao_shadows_lock);
+
+	// Set status to 'none'
+	instance->status = ao_status_none;
+	spin_unlock(&instance->subdevice_lock);
+
+	//Signal reset if user is on wait.
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me1600_ao_subdevice_t *instance;
+	uint16_t tmp;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	// Checking parameters.
+	if (flags) {
+		PERROR
+		    ("Invalid flag specified. Must be ME_IO_SINGLE_CONFIG_NO_FLAGS.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (trig_edge != ME_TRIG_EDGE_NONE) {
+		PERROR
+		    ("Invalid trigger edge. Software trigger has not edge. Must be ME_TRIG_EDGE_NONE\n");
+		return ME_ERRNO_INVALID_TRIG_EDGE;
+	}
+
+	if (trig_type != ME_TRIG_TYPE_SW) {
+		PERROR("Invalid trigger edge. Must be ME_TRIG_TYPE_SW.\n");
+		return ME_ERRNO_INVALID_TRIG_TYPE;
+	}
+
+	if ((trig_chan != ME_TRIG_CHAN_DEFAULT)
+	    && (trig_chan != ME_TRIG_CHAN_SYNCHRONOUS)) {
+		PERROR("Invalid trigger channel specified.\n");
+		return ME_ERRNO_INVALID_TRIG_CHAN;
+	}
+
+	if (ref != ME_REF_AO_GROUND) {
+		PERROR
+		    ("Invalid reference. Analog outputs have to have got REF_AO_GROUND.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if (((single_config + 1) >
+	     (instance->u_ranges_count + instance->i_ranges_count))
+	    || (single_config < 0)) {
+		PERROR("Invalid range specified.\n");
+		return ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+	// Checking parameters - done. All is fine. Do config.
+
+	ME_SUBDEVICE_ENTER;
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ao_shadows_lock);
+	(instance->ao_regs_shadows)->trigger &= ~(0x1 << instance->ao_idx);	//Cancell waiting for trigger.
+	(instance->ao_regs_shadows)->shadow[instance->ao_idx] = 0;
+	(instance->ao_regs_shadows)->mirror[instance->ao_idx] = 0;
+
+	spin_lock(instance->config_regs_lock);
+	switch (single_config) {
+	case 0:		// 0V 10V
+		tmp = inw(instance->current_on_reg);	// Volts
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->current_on_reg);
+		PDEBUG_REG("current_on_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->current_on_reg - instance->reg_base, tmp);
+
+		// 0V
+		outw(0,
+		     (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+		PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->registry[instance->
+								 ao_idx] -
+			   instance->reg_base, 0);
+
+		tmp = inw(instance->uni_bi_reg);	// unipolar
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->uni_bi_reg);
+		PDEBUG_REG("uni_bi_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->uni_bi_reg - instance->reg_base, tmp);
+
+		tmp = inw(instance->i_range_reg);	// 0..20mA <= If exists.
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->i_range_reg);
+		PDEBUG_REG("i_range_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->i_range_reg - instance->reg_base, tmp);
+		break;
+
+	case 1:		// -10V 10V
+		tmp = inw(instance->current_on_reg);	// Volts
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->current_on_reg);
+		PDEBUG_REG("current_on_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->current_on_reg - instance->reg_base, tmp);
+
+		// 0V
+		outw(0x0800,
+		     (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+		PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->registry[instance->
+								 ao_idx] -
+			   instance->reg_base, 0x0800);
+
+		tmp = inw(instance->uni_bi_reg);	// bipolar
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->uni_bi_reg);
+		PDEBUG_REG("uni_bi_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->uni_bi_reg - instance->reg_base, tmp);
+
+		tmp = inw(instance->i_range_reg);	// 0..20mA <= If exists.
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->i_range_reg);
+		PDEBUG_REG("i_range_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->i_range_reg - instance->reg_base, tmp);
+		break;
+
+	case 2:		// 0mA 20mA
+		tmp = inw(instance->current_on_reg);	// mAmpers
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->current_on_reg);
+		PDEBUG_REG("current_on_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->current_on_reg - instance->reg_base, tmp);
+
+		tmp = inw(instance->i_range_reg);	// 0..20mA
+		tmp &= ~(0x1 << instance->ao_idx);
+		outw(tmp, instance->i_range_reg);
+		PDEBUG_REG("i_range_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->i_range_reg - instance->reg_base, tmp);
+
+		// 0mA
+		outw(0,
+		     (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+		PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->registry[instance->
+								 ao_idx] -
+			   instance->reg_base, 0);
+
+		tmp = inw(instance->uni_bi_reg);	// unipolar
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->uni_bi_reg);
+		PDEBUG_REG("uni_bi_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->uni_bi_reg - instance->reg_base, tmp);
+		break;
+
+	case 3:		// 4mA 20mA
+		tmp = inw(instance->current_on_reg);	// mAmpers
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->current_on_reg);
+		PDEBUG_REG("current_on_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->current_on_reg - instance->reg_base, tmp);
+
+		tmp = inw(instance->i_range_reg);	// 4..20mA
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->i_range_reg);
+		PDEBUG_REG("i_range_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->i_range_reg - instance->reg_base, tmp);
+
+		// 4mA
+		outw(0,
+		     (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+		PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->registry[instance->
+								 ao_idx] -
+			   instance->reg_base, 0);
+
+		tmp = inw(instance->uni_bi_reg);	// unipolar
+		tmp |= (0x1 << instance->ao_idx);
+		outw(tmp, instance->uni_bi_reg);
+		PDEBUG_REG("uni_bi_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->uni_bi_reg - instance->reg_base, tmp);
+		break;
+	}
+
+	// Trigger output.
+	outw(0x0000, instance->sim_output_reg);
+	PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->sim_output_reg - instance->reg_base, 0x0000);
+	outw(0xFFFF, instance->sim_output_reg);
+	PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->sim_output_reg - instance->reg_base, 0xFFFF);
+
+	if (trig_chan == ME_TRIG_CHAN_DEFAULT) {	// Individual triggering.
+		(instance->ao_regs_shadows)->synchronous &=
+		    ~(0x1 << instance->ao_idx);
+		PDEBUG("Individual triggering.\n");
+	} else if (trig_chan == ME_TRIG_CHAN_SYNCHRONOUS) {	// Synchronous triggering.
+		(instance->ao_regs_shadows)->synchronous |=
+		    (0x1 << instance->ao_idx);
+		PDEBUG("Synchronous triggering.\n");
+	}
+	spin_unlock(instance->config_regs_lock);
+	spin_unlock(instance->ao_shadows_lock);
+
+	instance->status = ao_status_single_configured;
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me1600_ao_subdevice_t *instance;
+	unsigned long delay = 0;
+	unsigned long j = 0;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags & ~ME_IO_SINGLE_NONBLOCKING) {
+		PERROR("Invalid flag specified. %d\n", flags);
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if ((!flags) && ((instance->ao_regs_shadows)->trigger & instance->ao_idx)) {	//Blocking mode. Wait for software trigger.
+		if (time_out) {
+			delay = (time_out * HZ) / 1000;
+			if (delay == 0)
+				delay = 1;
+		}
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Events are signaled when status change. This procedure has own timeout.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (!((instance->
+						     ao_regs_shadows)->
+						    trigger & instance->
+						    ao_idx)),
+						 (delay) ? delay : LONG_MAX);
+
+		if (instance == ao_status_none) {	// Reset was called.
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+			PDEBUG("Timeout reached.\n");
+			err = ME_ERRNO_TIMEOUT;
+		}
+	}
+
+	*value = (instance->ao_regs_shadows)->mirror[instance->ao_idx];
+
+	return err;
+}
+
+static int me1600_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me1600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long delay = 0;
+	int i;
+	unsigned long j = 0;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags &
+	    ~(ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS |
+	      ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (value & ~ME1600_AO_MAX_DATA) {
+		PERROR("Invalid value provided.\n");
+		return ME_ERRNO_VALUE_OUT_OF_RANGE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+	(instance->ao_regs_shadows)->trigger &= ~(0x1 << instance->ao_idx);	//Cancell waiting for trigger.
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+	//Write value.
+	spin_lock(instance->ao_shadows_lock);
+	(instance->ao_regs_shadows)->shadow[instance->ao_idx] =
+	    (uint16_t) value;
+
+	if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	// Trigger all outputs from synchronous list.
+		for (i = 0; i < (instance->ao_regs_shadows)->count; i++) {
+			if (((instance->ao_regs_shadows)->synchronous & (0x1 << i)) || (i == instance->ao_idx)) {	// Set all from synchronous list to correct state.
+				PDEBUG
+				    ("Synchronous triggering: output %d. idx=%d\n",
+				     i, instance->ao_idx);
+				(instance->ao_regs_shadows)->mirror[i] =
+				    (instance->ao_regs_shadows)->shadow[i];
+
+				outw((instance->ao_regs_shadows)->shadow[i],
+				     (instance->ao_regs_shadows)->registry[i]);
+				PDEBUG_REG
+				    ("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     (instance->ao_regs_shadows)->registry[i] -
+				     instance->reg_base,
+				     (instance->ao_regs_shadows)->shadow[i]);
+
+				(instance->ao_regs_shadows)->trigger &=
+				    ~(0x1 << i);
+			}
+		}
+
+		// Trigger output.
+		outw(0x0000, instance->sim_output_reg);
+		PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->sim_output_reg - instance->reg_base, 0);
+		outw(0xFFFF, instance->sim_output_reg);
+		PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->sim_output_reg - instance->reg_base,
+			   0xFFFF);
+		instance->status = ao_status_single_end;
+	} else {		// Individual mode.
+		if ((instance->ao_regs_shadows)->synchronous & (0x1 << instance->ao_idx)) {	// Put on synchronous start list. Set output as waiting for trigger.
+			PDEBUG("Add to synchronous list. idx=%d\n",
+			       instance->ao_idx);
+			(instance->ao_regs_shadows)->trigger |=
+			    (0x1 << instance->ao_idx);
+			instance->status = ao_status_single_run;
+			PDEBUG("Synchronous list: 0x%x.\n",
+			       (instance->ao_regs_shadows)->synchronous);
+		} else {	// Fired this one.
+			PDEBUG("Triggering. idx=%d\n", instance->ao_idx);
+			(instance->ao_regs_shadows)->mirror[instance->ao_idx] =
+			    (instance->ao_regs_shadows)->shadow[instance->
+								ao_idx];
+
+			outw((instance->ao_regs_shadows)->
+			     shadow[instance->ao_idx],
+			     (instance->ao_regs_shadows)->registry[instance->
+								   ao_idx]);
+			PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   (instance->ao_regs_shadows)->
+				   registry[instance->ao_idx] -
+				   instance->reg_base,
+				   (instance->ao_regs_shadows)->
+				   shadow[instance->ao_idx]);
+
+			// Set output as triggered.
+			(instance->ao_regs_shadows)->trigger &=
+			    ~(0x1 << instance->ao_idx);
+
+			// Trigger output.
+			outw(0x0000, instance->sim_output_reg);
+			PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->sim_output_reg -
+				   instance->reg_base, 0);
+			outw(0xFFFF, instance->sim_output_reg);
+			PDEBUG_REG("sim_output_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->sim_output_reg -
+				   instance->reg_base, 0xFFFF);
+			instance->status = ao_status_single_end;
+		}
+	}
+	spin_unlock(instance->ao_shadows_lock);
+
+	//Init control task
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = jiffies;
+	instance->ao_control_task_flag = 1;
+	queue_delayed_work(instance->me1600_workqueue,
+			   &instance->ao_control_task, 1);
+
+	if ((!flags & ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING) && ((instance->ao_regs_shadows)->trigger & instance->ao_idx)) {	//Blocking mode. Wait for software trigger.
+		if (time_out) {
+			delay = (time_out * HZ) / 1000;
+			if (delay == 0)
+				delay = 1;
+		}
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Events are signaled when status change. This procedure has own timeout.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (!((instance->
+						     ao_regs_shadows)->
+						    trigger & instance->
+						    ao_idx)),
+						 (delay) ? delay : LONG_MAX);
+
+		if (instance == ao_status_none) {
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+			PDEBUG("Timeout reached.\n");
+			err = ME_ERRNO_TIMEOUT;
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1600_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	me1600_ao_subdevice_t *instance;
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*number = 1;		//Every subdevice has only 1 channel.
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_query_subdevice_type(me_subdevice_t * subdevice, int *type,
+					  int *subtype)
+{
+	me1600_ao_subdevice_t *instance;
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*type = ME_TYPE_AO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_AO_TRIG_SYNCHRONOUS;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range)
+{
+	me1600_ao_subdevice_t *instance;
+	int i;
+	int r = -1;
+	int diff = 21E6;
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((*max - *min) < 0) {
+		PERROR("Invalid minimum and maximum values specified.\n");
+		return ME_ERRNO_INVALID_MIN_MAX;
+	}
+	// Maximum ranges are slightly less then 10V or 20mA. For convenient we accepted this value as valid one.
+	if (unit == ME_UNIT_VOLT) {
+		for (i = 0; i < instance->u_ranges_count; i++) {
+			if ((instance->u_ranges[i].min <= *min)
+			    && ((instance->u_ranges[i].max + 5000) >= *max)) {
+				if ((instance->u_ranges[i].max -
+				     instance->u_ranges[i].min) - (*max -
+								   *min) <
+				    diff) {
+					r = i;
+					diff =
+					    (instance->u_ranges[i].max -
+					     instance->u_ranges[i].min) -
+					    (*max - *min);
+				}
+			}
+		}
+
+		if (r < 0) {
+			PERROR("No matching range found.\n");
+			return ME_ERRNO_NO_RANGE;
+		} else {
+			*min = instance->u_ranges[r].min;
+			*max = instance->u_ranges[r].max;
+			*range = r;
+		}
+	} else if (unit == ME_UNIT_AMPERE) {
+		for (i = 0; i < instance->i_ranges_count; i++) {
+			if ((instance->i_ranges[i].min <= *min)
+			    && (instance->i_ranges[i].max + 5000 >= *max)) {
+				if ((instance->i_ranges[i].max -
+				     instance->i_ranges[i].min) - (*max -
+								   *min) <
+				    diff) {
+					r = i;
+					diff =
+					    (instance->i_ranges[i].max -
+					     instance->i_ranges[i].min) -
+					    (*max - *min);
+				}
+			}
+		}
+
+		if (r < 0) {
+			PERROR("No matching range found.\n");
+			return ME_ERRNO_NO_RANGE;
+		} else {
+			*min = instance->i_ranges[r].min;
+			*max = instance->i_ranges[r].max;
+			*range = r + instance->u_ranges_count;
+		}
+	} else {
+		PERROR("Invalid physical unit specified.\n");
+		return ME_ERRNO_INVALID_UNIT;
+	}
+	*maxdata = ME1600_AO_MAX_DATA;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count)
+{
+	me1600_ao_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+	switch (unit) {
+	case ME_UNIT_VOLT:
+		*count = instance->u_ranges_count;
+		break;
+	case ME_UNIT_AMPERE:
+		*count = instance->i_ranges_count;
+		break;
+	case ME_UNIT_ANY:
+		*count = instance->u_ranges_count + instance->i_ranges_count;
+		break;
+	default:
+		*count = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1600_ao_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata)
+{
+	me1600_ao_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me1600_ao_subdevice_t *) subdevice;
+
+	if (((range + 1) >
+	     (instance->u_ranges_count + instance->i_ranges_count))
+	    || (range < 0)) {
+		PERROR("Invalid range number specified.\n");
+		return ME_ERRNO_INVALID_RANGE;
+	}
+
+	if (range < instance->u_ranges_count) {
+		*unit = ME_UNIT_VOLT;
+		*min = instance->u_ranges[range].min;
+		*max = instance->u_ranges[range].max;
+	} else if (range < instance->u_ranges_count + instance->i_ranges_count) {
+		*unit = ME_UNIT_AMPERE;
+		*min = instance->i_ranges[range - instance->u_ranges_count].min;
+		*max = instance->i_ranges[range - instance->u_ranges_count].max;
+	}
+	*maxdata = ME1600_AO_MAX_DATA;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void me1600_ao_work_control_task(void *subdevice)
+#else
+static void me1600_ao_work_control_task(struct work_struct *work)
+#endif
+{
+	me1600_ao_subdevice_t *instance;
+	int reschedule = 1;
+	int signaling = 0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	instance = (me1600_ao_subdevice_t *) subdevice;
+#else
+	instance =
+	    container_of((void *)work, me1600_ao_subdevice_t, ao_control_task);
+#endif
+
+	PINFO("<%s: %ld> executed. idx=%d\n", __FUNCTION__, jiffies,
+	      instance->ao_idx);
+
+	if (!((instance->ao_regs_shadows)->trigger & instance->ao_idx)) {	// Output was triggerd.
+		// Signal the end.
+		signaling = 1;
+		reschedule = 0;
+		if (instance->status == ao_status_single_run) {
+			instance->status = ao_status_single_end;
+		}
+
+	} else if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+		PDEBUG("Timeout reached.\n");
+		spin_lock(instance->ao_shadows_lock);
+		// Restore old settings.
+		PDEBUG("Write old value back to register.\n");
+		(instance->ao_regs_shadows)->shadow[instance->ao_idx] =
+		    (instance->ao_regs_shadows)->mirror[instance->ao_idx];
+
+		outw((instance->ao_regs_shadows)->mirror[instance->ao_idx],
+		     (instance->ao_regs_shadows)->registry[instance->ao_idx]);
+		PDEBUG_REG("channel_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->registry[instance->
+								 ao_idx] -
+			   instance->reg_base,
+			   (instance->ao_regs_shadows)->mirror[instance->
+							       ao_idx]);
+
+		//Remove from synchronous strt list.
+		(instance->ao_regs_shadows)->trigger &=
+		    ~(0x1 << instance->ao_idx);
+		if (instance->status == ao_status_none) {
+			instance->status = ao_status_single_end;
+		}
+		spin_unlock(instance->ao_shadows_lock);
+
+		// Signal the end.
+		signaling = 1;
+		reschedule = 0;
+	}
+
+	if (signaling) {	//Signal it.
+		wake_up_interruptible_all(&instance->wait_queue);
+	}
+
+	if (instance->ao_control_task_flag && reschedule) {	// Reschedule task
+		queue_delayed_work(instance->me1600_workqueue,
+				   &instance->ao_control_task, 1);
+	} else {
+		PINFO("<%s> Ending control task.\n", __FUNCTION__);
+	}
+
+}

+ 132 - 0
drivers/staging/meilhaus/me1600_ao.h

@@ -0,0 +1,132 @@
+/**
+ * @file me1600_ao.h
+ *
+ * @brief Meilhaus ME-1600 analog output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1600_AO_H_
+#define _ME1600_AO_H_
+
+# include <linux/version.h>
+# include "mesubdevice.h"
+
+# ifdef __KERNEL__
+
+#  define ME1600_MAX_RANGES	2	/**< Specifies the maximum number of ranges in me1600_ao_subdevice_t::u_ranges und me1600_ao_subdevice_t::i_ranges. */
+
+/**
+ * @brief Defines a entry in the range table.
+ */
+typedef struct me1600_ao_range_entry {
+	int32_t min;
+	int32_t max;
+} me1600_ao_range_entry_t;
+
+typedef struct me1600_ao_timeout {
+	unsigned long start_time;
+	unsigned long delay;
+} me1600_ao_timeout_t;
+
+typedef struct me1600_ao_shadow {
+	int count;
+	unsigned long *registry;
+	uint16_t *shadow;
+	uint16_t *mirror;
+	uint16_t synchronous;									/**< Synchronization list. */
+	uint16_t trigger;										/**< Synchronization flag. */
+} me1600_ao_shadow_t;
+
+typedef enum ME1600_AO_STATUS {
+	ao_status_none = 0,
+	ao_status_single_configured,
+	ao_status_single_run,
+	ao_status_single_end,
+	ao_status_last
+} ME1600_AO_STATUS;
+
+/**
+ * @brief The ME-1600 analog output subdevice class.
+ */
+typedef struct me1600_ao_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;									/**< The subdevice base class. */
+
+	/* Attributes */
+	int ao_idx;												/**< The index of the analog output subdevice on the device. */
+
+	spinlock_t subdevice_lock;								/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *config_regs_lock;							/**< Spin lock to protect configuration registers from concurrent access. */
+
+	int u_ranges_count;										/**< The number of voltage ranges available on this subdevice. */
+	me1600_ao_range_entry_t u_ranges[ME1600_MAX_RANGES];	/**< Array holding the voltage ranges on this subdevice. */
+	int i_ranges_count;										/**< The number of current ranges available on this subdevice. */
+	me1600_ao_range_entry_t i_ranges[ME1600_MAX_RANGES];	/**< Array holding the current ranges on this subdevice. */
+
+	/* Registers */
+	unsigned long uni_bi_reg;								/**< Register for switching between unipoar and bipolar output mode. */
+	unsigned long i_range_reg;								/**< Register for switching between ranges. */
+	unsigned long sim_output_reg;							/**< Register used in order to update all channels simultaneously. */
+	unsigned long current_on_reg;							/**< Register enabling current output on the fourth subdevice. */
+#   ifdef PDEBUG_REG
+	unsigned long reg_base;
+#   endif
+
+	ME1600_AO_STATUS status;
+	me1600_ao_shadow_t *ao_regs_shadows;					/**< Addresses and shadows of output's registers. */
+	spinlock_t *ao_shadows_lock;							/**< Protects the shadow's struct. */
+	int mode;												/**< Mode in witch output should works. */
+	wait_queue_head_t wait_queue;							/**< Wait queue to put on tasks waiting for data to arrive. */
+	me1600_ao_timeout_t timeout;							/**< The timeout for start in blocking and non-blocking mode. */
+	struct workqueue_struct *me1600_workqueue;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct ao_control_task;
+#else
+	struct delayed_work ao_control_task;
+#endif
+
+	volatile int ao_control_task_flag;						/**< Flag controling reexecuting of control task */
+} me1600_ao_subdevice_t;
+
+/**
+ * @brief The constructor to generate a subdevice template instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param ao_idx The index of the analog output subdevice on the device.
+ * @param current Flag indicating that analog output with #ao_idx of 3 is capable of current output.
+ * @param config_regs_lock Pointer to spin lock protecting the configuration registers and from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me1600_ao_subdevice_t *me1600_ao_constructor(uint32_t reg_base,
+					     unsigned int ao_idx,
+					     int curr,
+					     spinlock_t * config_regs_lock,
+					     spinlock_t * ao_shadows_lock,
+					     me1600_ao_shadow_t *
+					     ao_regs_shadows,
+					     struct workqueue_struct
+					     *me1600_wq);
+
+# endif	//__KERNEL__
+#endif //_ME1600_AO_H_

+ 66 - 0
drivers/staging/meilhaus/me1600_ao_reg.h

@@ -0,0 +1,66 @@
+/**
+ * @file me1600_ao_reg.h
+ *
+ * @brief ME-1600 analog output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1600_AO_REG_H_
+#define _ME1600_AO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME1600_CHANNEL_0_REG    0x00	/**< Register to set a digital value on channel 0. */
+#define ME1600_CHANNEL_1_REG    0x02	/**< Register to set a digital value on channel 1. */
+#define ME1600_CHANNEL_2_REG    0x04	/**< Register to set a digital value on channel 2. */
+#define ME1600_CHANNEL_3_REG    0x06	/**< Register to set a digital value on channel 3. */
+#define ME1600_CHANNEL_4_REG    0x08	/**< Register to set a digital value on channel 4. */
+#define ME1600_CHANNEL_5_REG    0x0A	/**< Register to set a digital value on channel 5. */
+#define ME1600_CHANNEL_6_REG    0x0C	/**< Register to set a digital value on channel 6. */
+#define ME1600_CHANNEL_7_REG    0x0E	/**< Register to set a digital value on channel 7. */
+#define ME1600_CHANNEL_8_REG    0x10	/**< Register to set a digital value on channel 8. */
+#define ME1600_CHANNEL_9_REG    0x12	/**< Register to set a digital value on channel 9. */
+#define ME1600_CHANNEL_10_REG   0x14	/**< Register to set a digital value on channel 10. */
+#define ME1600_CHANNEL_11_REG   0x16	/**< Register to set a digital value on channel 11. */
+#define ME1600_CHANNEL_12_REG   0x18	/**< Register to set a digital value on channel 12. */
+#define ME1600_CHANNEL_13_REG   0x1A	/**< Register to set a digital value on channel 13. */
+#define ME1600_CHANNEL_14_REG   0x1C	/**< Register to set a digital value on channel 14. */
+#define ME1600_CHANNEL_15_REG   0x1E	/**< Register to set a digital value on channel 15. */
+
+/* Every channel one bit: bipolar = 0, unipolar = 1 */
+#define ME1600_UNI_BI_REG		0x20	/**< Register to switch between unipolar and bipolar. */
+
+/* Every channel one bit (only lower 8 Bits): 0..20mA = 0, 4..20mA = 1 */
+#define ME1600_020_420_REG		0x22	/**< Register to switch between the two current ranges. */
+
+/* If a bit is set, the corresponding DAC (4 ports each) is
+   not set at the moment you write to an output of it.
+   Clearing the bit updates the port. */
+#define ME1600_SIM_OUTPUT_REG	0x24	/**< Register to update all channels of a subdevice simultaneously. */
+
+/* Current on/off (only lower 8 bits): off = 0, on  = 1 */
+#define ME1600_CURRENT_ON_REG	0x26	/**< Register to swicht between voltage and current output. */
+
+#define ME1600_AO_MAX_DATA		0x0FFF	/**< The maximum digital data accepted by an analog output channel. */
+
+#endif
+#endif

+ 261 - 0
drivers/staging/meilhaus/me1600_device.c

@@ -0,0 +1,261 @@
+/**
+ * @file me1600_device.c
+ *
+ * @brief ME-1600 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "mesubdevice.h"
+#include "me1600_device.h"
+
+static void me1600_set_registry(me1600_device_t * subdevice, uint32_t reg_base);
+static void me1600_destructor(struct me_device *device);
+
+/**
+ * @brief Global variable.
+ * This is working queue for runing a separate atask that will be responsible for work status (start, stop, timeouts).
+ */
+static struct workqueue_struct *me1600_workqueue;
+
+me_device_t *me1600_pci_constructor(struct pci_dev *pci_device)
+{
+	int err;
+	me1600_device_t *me1600_device;
+	me_subdevice_t *subdevice;
+	unsigned int chip_idx;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me1600_device = kmalloc(sizeof(me1600_device_t), GFP_KERNEL);
+
+	if (!me1600_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me1600_device, 0, sizeof(me1600_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me1600_device, pci_device);
+
+	if (err) {
+		kfree(me1600_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+	// Initialize spin lock .
+	spin_lock_init(&me1600_device->config_regs_lock);
+	spin_lock_init(&me1600_device->ao_shadows_lock);
+
+	// Get the number of analog output subdevices.
+	chip_idx =
+	    me1600_versions_get_device_index(me1600_device->base.info.pci.
+					     device_id);
+
+	// Create shadow instance.
+	me1600_device->ao_regs_shadows.count =
+	    me1600_versions[chip_idx].ao_chips;
+	me1600_device->ao_regs_shadows.registry =
+	    kmalloc(me1600_versions[chip_idx].ao_chips * sizeof(unsigned long),
+		    GFP_KERNEL);
+	me1600_set_registry(me1600_device,
+			    me1600_device->base.info.pci.reg_bases[2]);
+	me1600_device->ao_regs_shadows.shadow =
+	    kmalloc(me1600_versions[chip_idx].ao_chips * sizeof(uint16_t),
+		    GFP_KERNEL);
+	me1600_device->ao_regs_shadows.mirror =
+	    kmalloc(me1600_versions[chip_idx].ao_chips * sizeof(uint16_t),
+		    GFP_KERNEL);
+
+	// Create subdevice instances.
+	for (i = 0; i < me1600_versions[chip_idx].ao_chips; i++) {
+		subdevice =
+		    (me_subdevice_t *) me1600_ao_constructor(me1600_device->
+							     base.info.pci.
+							     reg_bases[2], i,
+							     ((me1600_versions
+							       [chip_idx].curr >
+							       i) ? 1 : 0),
+							     &me1600_device->
+							     config_regs_lock,
+							     &me1600_device->
+							     ao_shadows_lock,
+							     &me1600_device->
+							     ao_regs_shadows,
+							     me1600_workqueue);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me1600_device);
+			kfree(me1600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me1600_device->base.slist,
+					    subdevice);
+	}
+
+	// Overwrite base class methods.
+	me1600_device->base.me_device_destructor = me1600_destructor;
+
+	return (me_device_t *) me1600_device;
+}
+
+static void me1600_destructor(struct me_device *device)
+{
+	me1600_device_t *me1600_device = (me1600_device_t *) device;
+	PDEBUG("executed.\n");
+
+	// Destroy shadow instance.
+	kfree(me1600_device->ao_regs_shadows.registry);
+	kfree(me1600_device->ao_regs_shadows.shadow);
+	kfree(me1600_device->ao_regs_shadows.mirror);
+
+	me_device_deinit((me_device_t *) me1600_device);
+	kfree(me1600_device);
+}
+
+static void me1600_set_registry(me1600_device_t * subdevice, uint32_t reg_base)
+{				// Create shadow structure.
+	if (subdevice->ao_regs_shadows.count >= 1) {
+		subdevice->ao_regs_shadows.registry[0] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_0_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 2) {
+		subdevice->ao_regs_shadows.registry[1] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_1_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 3) {
+		subdevice->ao_regs_shadows.registry[2] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_2_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 4) {
+		subdevice->ao_regs_shadows.registry[3] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_3_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 5) {
+		subdevice->ao_regs_shadows.registry[4] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_4_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 6) {
+		subdevice->ao_regs_shadows.registry[5] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_5_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 7) {
+		subdevice->ao_regs_shadows.registry[6] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_6_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 8) {
+		subdevice->ao_regs_shadows.registry[7] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_7_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 9) {
+		subdevice->ao_regs_shadows.registry[8] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_8_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 10) {
+		subdevice->ao_regs_shadows.registry[9] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_9_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 11) {
+		subdevice->ao_regs_shadows.registry[10] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_10_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 12) {
+		subdevice->ao_regs_shadows.registry[11] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_11_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 13) {
+		subdevice->ao_regs_shadows.registry[12] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_12_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 14) {
+		subdevice->ao_regs_shadows.registry[13] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_13_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 15) {
+		subdevice->ao_regs_shadows.registry[14] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_14_REG);
+	}
+	if (subdevice->ao_regs_shadows.count >= 16) {
+		subdevice->ao_regs_shadows.registry[15] =
+		    (unsigned long)(reg_base + ME1600_CHANNEL_15_REG);
+	}
+	if (subdevice->ao_regs_shadows.count > 16) {
+		PERROR("More than 16 outputs! (%d)\n",
+		       subdevice->ao_regs_shadows.count);
+	}
+}
+
+// Init and exit of module.
+
+static int __init me1600_init(void)
+{
+	PDEBUG("executed\n.");
+
+	me1600_workqueue = create_singlethread_workqueue("me1600");
+	return 0;
+}
+
+static void __exit me1600_exit(void)
+{
+	PDEBUG("executed\n.");
+
+	flush_workqueue(me1600_workqueue);
+	destroy_workqueue(me1600_workqueue);
+}
+
+module_init(me1600_init);
+module_exit(me1600_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for ME-1600 Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-1600 Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me1600_pci_constructor);

+ 101 - 0
drivers/staging/meilhaus/me1600_device.h

@@ -0,0 +1,101 @@
+/**
+ * @file me1600_device.h
+ *
+ * @brief ME-1600 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME1600_H
+#define _ME1600_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+#include "me1600_ao.h"
+#include "me1600_ao_reg.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure to store device capabilities.
+ */
+typedef struct me1600_version {
+	uint16_t device_id;				/**< The PCI device id of the device. */
+	unsigned int ao_chips;			/**< The number of analog outputs on the device. */
+	int curr;						/**< Flag to identify amounts of current output. */
+} me1600_version_t;
+
+/**
+  * @brief Defines for each ME-1600 device version its capabilities.
+ */
+static me1600_version_t me1600_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME1600_4U, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME1600_8U, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME1600_12U, 12, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME1600_16U, 16, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME1600_16U_8I, 16, 8},
+	{0}
+};
+
+/**< Returns the number of entries in #me1600_versions. */
+#define ME1600_DEVICE_VERSIONS (sizeof(me1600_versions) / sizeof(me1600_version_t) - 1)
+
+/**
+ * @brief Returns the index of the device entry in #me1600_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me1600_versions.
+ */
+static inline unsigned int me1600_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME1600_DEVICE_VERSIONS; i++)
+		if (me1600_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-1600 device class structure.
+ */
+typedef struct me1600_device {
+	me_device_t base;						/**< The Meilhaus device base class. */
+	spinlock_t config_regs_lock;			/**< Protects the configuration registers. */
+
+	me1600_ao_shadow_t ao_regs_shadows;		/**< Addresses and shadows of output's registers. */
+	spinlock_t ao_shadows_lock;				/**< Protects the shadow's struct. */
+} me1600_device_t;
+
+/**
+ * @brief The ME-1600 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-1600 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me1600_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 3434 - 0
drivers/staging/meilhaus/me4600_ai.c

@@ -0,0 +1,3434 @@
+/**
+ * @file me4600_ai.c
+ *
+ * @brief ME-4000 analog input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+# define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "medebug.h"
+#include "meids.h"
+
+#include "me4600_reg.h"
+#include "me4600_ai_reg.h"
+#include "me4600_ai.h"
+
+/*
+ * Declarations (local)
+ */
+
+static void me4600_ai_destructor(struct me_subdevice *subdevice);
+static int me4600_ai_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags);
+
+static int me4600_ai_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags);
+
+static int me4600_ai_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags);
+
+static int me4600_ai_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags);
+static int me4600_ai_io_stream_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int read_mode,
+				    int *values, int *count, int flags);
+static int me4600_ai_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags);
+static int inline me4600_ai_io_stream_read_get_value(me4600_ai_subdevice_t *
+						     instance, int *values,
+						     const int count,
+						     const int flags);
+
+static int me4600_ai_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags);
+static int me4600_ai_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags);
+static int me4600_ai_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags);
+
+static int me4600_ai_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range);
+static int me4600_ai_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count);
+static int me4600_ai_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata);
+static int me4600_ai_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks);
+static int me4600_ai_query_number_channels(me_subdevice_t * subdevice,
+					   int *number);
+static int me4600_ai_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype);
+static int me4600_ai_query_subdevice_caps(me_subdevice_t * subdevice,
+					  int *caps);
+static int me4600_ai_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me4600_ai_isr(int irq, void *dev_id);
+#else
+static irqreturn_t me4600_ai_isr(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+static int ai_mux_toggler(me4600_ai_subdevice_t * subdevice);
+
+/** Immidiate stop.
+* Reset all IRQ's sources. (block laches)
+* Preserve FIFO
+*/
+static int ai_stop_immediately(me4600_ai_subdevice_t * instance);
+
+/** Immidiate stop.
+* Reset all IRQ's sources. (block laches)
+* Reset data FIFO
+*/
+void inline ai_stop_isr(me4600_ai_subdevice_t * instance);
+
+/** Interrupt logics.
+* Read datas
+* Reset latches
+*/
+void ai_limited_isr(me4600_ai_subdevice_t * instance, const uint32_t irq_status,
+		    const uint32_t ctrl_status);
+void ai_infinite_isr(me4600_ai_subdevice_t * instance,
+		     const uint32_t irq_status, const uint32_t ctrl_status);
+
+/** Last chunck of datas. We must reschedule sample counter.
+* Leaving SC_RELOAD doesn't do any harm, but in some bad case can make extra interrupts.
+* When threshold is wrongly set some IRQ are lost.(!!!)
+*/
+void inline ai_reschedule_SC(me4600_ai_subdevice_t * instance);
+
+/** Read datas from FIFO and copy them to buffer */
+static int inline ai_read_data(me4600_ai_subdevice_t * instance,
+			       const int count);
+
+/** Copy rest of data from fifo to circular buffer.*/
+static int inline ai_read_data_pooling(me4600_ai_subdevice_t * instance);
+
+/** Set ISM to next state for infinite data aqusation mode*/
+void inline ai_infinite_ISM(me4600_ai_subdevice_t * instance);
+
+/** Set ISM to next state for define amount of data aqusation mode*/
+void inline ai_limited_ISM(me4600_ai_subdevice_t * instance,
+			   uint32_t irq_status);
+
+/** Set ISM to next stage for limited mode */
+void inline ai_data_acquisition_logic(me4600_ai_subdevice_t * instance);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void me4600_ai_work_control_task(void *subdevice);
+#else
+static void me4600_ai_work_control_task(struct work_struct *work);
+#endif
+
+/* Definitions
+ */
+
+me4600_ai_subdevice_t *me4600_ai_constructor(uint32_t reg_base,
+					     unsigned int channels,
+					     unsigned int ranges,
+					     int isolated,
+					     int sh,
+					     int irq,
+					     spinlock_t * ctrl_reg_lock,
+					     struct workqueue_struct *me4600_wq)
+{
+	me4600_ai_subdevice_t *subdevice;
+	int err;
+	unsigned int i;
+
+	PDEBUG("executed. idx=0\n");
+
+	// Allocate memory for subdevice instance.
+	subdevice = kmalloc(sizeof(me4600_ai_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_ai_subdevice_t));
+
+	// Initialize subdevice base class.
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	// Initialize circular buffer.
+	subdevice->circ_buf.mask = ME4600_AI_CIRC_BUF_COUNT - 1;
+
+	subdevice->circ_buf.buf =
+	    (void *)__get_free_pages(GFP_KERNEL, ME4600_AI_CIRC_BUF_SIZE_ORDER);
+	PDEBUG("circ_buf = %p size=%ld\n", subdevice->circ_buf.buf,
+	       ME4600_AI_CIRC_BUF_SIZE);
+
+	if (!subdevice->circ_buf.buf) {
+		PERROR("Cannot get circular buffer.\n");
+		me_subdevice_deinit((me_subdevice_t *) subdevice);
+		kfree(subdevice);
+		return NULL;
+	}
+
+	memset(subdevice->circ_buf.buf, 0, ME4600_AI_CIRC_BUF_SIZE);
+	subdevice->circ_buf.head = 0;
+	subdevice->circ_buf.tail = 0;
+	subdevice->status = ai_status_none;
+
+	// Initialize wait queue.
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	// Save the number of channels.
+	subdevice->channels = channels;
+
+	/* Initialize the single config entries to reset values */
+	for (i = 0; i < channels; i++) {
+		subdevice->single_config[i].status = ME_SINGLE_CHANNEL_NOT_CONFIGURED;	//not configured
+	}
+
+	// Save if isolated device.
+	subdevice->isolated = isolated;
+
+	// Save if sample and hold is available.
+	subdevice->sh = sh;
+
+	// Set stream config to not configured state.
+	subdevice->fifo_irq_threshold = 0;
+	subdevice->data_required = 0;
+	subdevice->chan_list_len = 0;
+
+	// Initialize registers addresses.
+	subdevice->ctrl_reg = reg_base + ME4600_AI_CTRL_REG;
+	subdevice->status_reg = reg_base + ME4600_AI_STATUS_REG;
+	subdevice->channel_list_reg = reg_base + ME4600_AI_CHANNEL_LIST_REG;
+	subdevice->data_reg = reg_base + ME4600_AI_DATA_REG;
+	subdevice->chan_timer_reg = reg_base + ME4600_AI_CHAN_TIMER_REG;
+	subdevice->chan_pre_timer_reg = reg_base + ME4600_AI_CHAN_PRE_TIMER_REG;
+	subdevice->scan_timer_low_reg = reg_base + ME4600_AI_SCAN_TIMER_LOW_REG;
+	subdevice->scan_timer_high_reg =
+	    reg_base + ME4600_AI_SCAN_TIMER_HIGH_REG;
+	subdevice->scan_pre_timer_low_reg =
+	    reg_base + ME4600_AI_SCAN_PRE_TIMER_LOW_REG;
+	subdevice->scan_pre_timer_high_reg =
+	    reg_base + ME4600_AI_SCAN_PRE_TIMER_HIGH_REG;
+	subdevice->start_reg = reg_base + ME4600_AI_START_REG;
+	subdevice->irq_status_reg = reg_base + ME4600_IRQ_STATUS_REG;
+	subdevice->sample_counter_reg = reg_base + ME4600_AI_SAMPLE_COUNTER_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	// Initialize ranges.
+	subdevice->ranges_len = ranges;
+	subdevice->ranges[0].min = -10E6;
+	subdevice->ranges[0].max = 9999694;
+
+	subdevice->ranges[1].min = 0;
+	subdevice->ranges[1].max = 9999847;
+
+	subdevice->ranges[2].min = -25E5;
+	subdevice->ranges[2].max = 2499923;
+
+	subdevice->ranges[3].min = 0;
+	subdevice->ranges[3].max = 2499961;
+
+	// We have to switch the mux in order to get it work correctly.
+	ai_mux_toggler(subdevice);
+
+	// Register interrupt service routine.
+	subdevice->irq = irq;
+	if (request_irq(subdevice->irq, me4600_ai_isr,
+#ifdef IRQF_DISABLED
+			IRQF_DISABLED | IRQF_SHARED,
+#else
+			SA_INTERRUPT | SA_SHIRQ,
+#endif
+			ME4600_NAME, subdevice)) {
+		PERROR("Cannot register interrupt service routine.\n");
+		me_subdevice_deinit((me_subdevice_t *) subdevice);
+		free_pages((unsigned long)subdevice->circ_buf.buf,
+			   ME4600_AI_CIRC_BUF_SIZE_ORDER);
+		subdevice->circ_buf.buf = NULL;
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", subdevice->irq);
+
+	// Override base class methods.
+	subdevice->base.me_subdevice_destructor = me4600_ai_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_ai_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_ai_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_ai_io_single_read;
+	subdevice->base.me_subdevice_io_stream_config =
+	    me4600_ai_io_stream_config;
+	subdevice->base.me_subdevice_io_stream_new_values =
+	    me4600_ai_io_stream_new_values;
+	subdevice->base.me_subdevice_io_stream_read = me4600_ai_io_stream_read;
+	subdevice->base.me_subdevice_io_stream_start =
+	    me4600_ai_io_stream_start;
+	subdevice->base.me_subdevice_io_stream_status =
+	    me4600_ai_io_stream_status;
+	subdevice->base.me_subdevice_io_stream_stop = me4600_ai_io_stream_stop;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_ai_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_ai_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_ai_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me4600_ai_query_subdevice_caps_args;
+	subdevice->base.me_subdevice_query_range_by_min_max =
+	    me4600_ai_query_range_by_min_max;
+	subdevice->base.me_subdevice_query_number_ranges =
+	    me4600_ai_query_number_ranges;
+	subdevice->base.me_subdevice_query_range_info =
+	    me4600_ai_query_range_info;
+	subdevice->base.me_subdevice_query_timer = me4600_ai_query_timer;
+
+	// Prepare work queue.
+	subdevice->me4600_workqueue = me4600_wq;
+
+/* workqueue API changed in kernel 2.6.20 */
+#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) )
+	INIT_WORK(&subdevice->ai_control_task, me4600_ai_work_control_task,
+		  (void *)subdevice);
+#else
+	INIT_DELAYED_WORK(&subdevice->ai_control_task,
+			  me4600_ai_work_control_task);
+#endif
+
+	return subdevice;
+}
+
+static void me4600_ai_destructor(struct me_subdevice *subdevice)
+{
+	me4600_ai_subdevice_t *instance;
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance->ai_control_task_flag = 0;
+	// Reset subdevice to asure clean exit.
+	me4600_ai_io_reset_subdevice(subdevice, NULL,
+				     ME_IO_RESET_SUBDEVICE_NO_FLAGS);
+
+	// Remove any tasks from work queue. This is paranoic because it was done allready in reset().
+	if (!cancel_delayed_work(&instance->ai_control_task)) {	//Wait 2 ticks to be sure that control task is removed from queue.
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(2);
+	}
+
+	free_irq(instance->irq, instance);
+	free_pages((unsigned long)instance->circ_buf.buf,
+		   ME4600_AI_CIRC_BUF_SIZE_ORDER);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+static int me4600_ai_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	volatile uint32_t ctrl;
+	unsigned long status;
+	const int timeout = HZ / 10;	//100ms
+	int i;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	instance->ai_control_task_flag = 0;
+	instance->status = ai_status_none;
+
+	for (i = 0; i <= timeout; i++) {
+		spin_lock_irqsave(instance->ctrl_reg_lock, status);
+		ctrl = inl(instance->ctrl_reg);
+		//Stop DMA
+		ctrl &= ~ME4600_AI_CTRL_RPCI_FIFO;
+		// Stop all actions. No conditions!
+		ctrl &= ~ME4600_AI_CTRL_BIT_STOP;
+		ctrl |= ME4600_AI_CTRL_BIT_IMMEDIATE_STOP;
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, status);
+
+		if (!(inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM))
+			break;
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	if (i > timeout) {
+		PERROR("FSM is still busy.\n");
+		ME_SUBDEVICE_EXIT;
+		return ME_ERRNO_INTERNAL;
+	}
+
+	spin_lock_irqsave(instance->ctrl_reg_lock, status);
+	ctrl = inl(instance->ctrl_reg);
+	// Clear all features. Dissable interrupts.
+	ctrl &= ~(ME4600_AI_CTRL_BIT_STOP
+		  | ME4600_AI_CTRL_BIT_LE_IRQ
+		  | ME4600_AI_CTRL_BIT_HF_IRQ | ME4600_AI_CTRL_BIT_SC_IRQ);
+	ctrl |= (ME4600_AI_CTRL_BIT_IMMEDIATE_STOP
+		 | ME4600_AI_CTRL_BIT_LE_IRQ_RESET
+		 | ME4600_AI_CTRL_BIT_HF_IRQ_RESET
+		 | ME4600_AI_CTRL_BIT_SC_IRQ_RESET);
+
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, status);
+
+	outl(ME4600_AI_MIN_CHAN_TICKS - 1, instance->chan_timer_reg);
+	PDEBUG_REG("chan_timer_reg outl(0x%lX+0x%lX)=0x%llx\n",
+		   instance->reg_base,
+		   instance->chan_timer_reg - instance->reg_base,
+		   ME4600_AI_MIN_CHAN_TICKS);
+	outl(ME4600_AI_MIN_ACQ_TICKS - 1, instance->chan_pre_timer_reg);
+	PDEBUG_REG("chan_pre_timer_reg outl(0x%lX+0x%lX)=0x%llx\n",
+		   instance->reg_base,
+		   instance->chan_pre_timer_reg - instance->reg_base,
+		   ME4600_AI_MIN_ACQ_TICKS);
+	outl(0, instance->scan_timer_low_reg);
+	PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_timer_high_reg);
+	PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_high_reg - instance->reg_base, 0);
+	outl(0, instance->scan_pre_timer_low_reg);
+	PDEBUG_REG("scan_pre_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_pre_timer_high_reg);
+	PDEBUG_REG("scan_pre_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_high_reg - instance->reg_base, 0);
+	outl(0xEFFFFFFF, instance->sample_counter_reg);
+	PDEBUG_REG("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->sample_counter_reg - instance->reg_base,
+		   0xEFFFFFFF);
+
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+
+	instance->fifo_irq_threshold = 0;
+	instance->data_required = 0;
+	instance->chan_list_len = 0;
+
+	// Initialize the single config entries to reset values.
+	for (i = 0; i < instance->channels; i++) {
+		instance->single_config[i].status =
+		    ME_SINGLE_CHANNEL_NOT_CONFIGURED;
+	}
+	instance->status = ai_status_none;
+
+	//Signal reset if user is on wait.
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags;
+	int i;
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (flags & ~ME_IO_SINGLE_CONFIG_CONTINUE) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	switch (trig_type) {
+	case ME_TRIG_TYPE_SW:
+		if (trig_edge != ME_TRIG_EDGE_NONE) {
+			PERROR
+			    ("Invalid trigger edge. Software trigger has not edge.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		if (instance->channels <= 16)	//Only versions with 32 channels have analog trigger (4670 and 4680)
+		{
+			PERROR("Invalid trigger type specified.\n");
+			return ME_ERRNO_INVALID_TRIG_TYPE;
+		}
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		if ((trig_edge != ME_TRIG_EDGE_ANY)
+		    && (trig_edge != ME_TRIG_EDGE_RISING)
+		    && (trig_edge != ME_TRIG_EDGE_FALLING)) {
+			PERROR("Invalid trigger edge specified.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+		break;
+
+	default:
+		PERROR("Invalid trigger type specified.\n");
+		return ME_ERRNO_INVALID_TRIG_TYPE;
+	}
+
+	if (trig_chan != ME_TRIG_CHAN_DEFAULT) {
+		PERROR("Invalid trigger channel specified.\n");
+		return ME_ERRNO_INVALID_TRIG_CHAN;
+	}
+
+	if ((single_config < 0) || (single_config >= instance->ranges_len)) {
+		PERROR("Invalid single config specified.\n");
+		return ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+
+	if ((ref != ME_REF_AI_GROUND) && (ref != ME_REF_AI_DIFFERENTIAL)) {
+		PERROR("Invalid analog reference specified.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if ((single_config % 2) && (ref != ME_REF_AI_GROUND)) {
+		PERROR("Invalid analog reference specified.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if ((ref == ME_REF_AI_DIFFERENTIAL)
+	    && ((instance->channels == 16) || (channel >= 16))) {
+		PERROR("Invalid analog reference specified.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if (channel < 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (channel >= instance->channels) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	//Prepare data entry.
+	// Common for all modes.
+	instance->single_config[channel].entry =
+	    channel | ME4600_AI_LIST_LAST_ENTRY;
+
+	if (ref == ME_REF_AI_DIFFERENTIAL) {	// ME_REF_AI_DIFFERENTIAL
+		instance->single_config[channel].entry |=
+		    ME4600_AI_LIST_INPUT_DIFFERENTIAL;
+	}
+/*
+		// ME4600_AI_LIST_INPUT_SINGLE_ENDED = 0x0000
+		// 'entry |= ME4600_AI_LIST_INPUT_SINGLE_ENDED' <== Do nothing. Removed.
+		else
+		{// ME_REF_AI_GROUND
+			instance->single_config[channel].entry |= ME4600_AI_LIST_INPUT_SINGLE_ENDED;
+		}
+*/
+	switch (single_config) {
+	case 0:		//-10V..10V
+/*
+					// ME4600_AI_LIST_RANGE_BIPOLAR_10 = 0x0000
+					// 'entry |= ME4600_AI_LIST_RANGE_BIPOLAR_10' <== Do nothing. Removed.
+					instance->single_config[channel].entry |= ME4600_AI_LIST_RANGE_BIPOLAR_10;
+*/ break;
+
+	case 1:		//0V..10V
+		instance->single_config[channel].entry |=
+		    ME4600_AI_LIST_RANGE_UNIPOLAR_10;
+		break;
+
+	case 2:		//-2.5V..2.5V
+		instance->single_config[channel].entry |=
+		    ME4600_AI_LIST_RANGE_BIPOLAR_2_5;
+		break;
+
+	case 3:		//0V..2.5V
+		instance->single_config[channel].entry |=
+		    ME4600_AI_LIST_RANGE_UNIPOLAR_2_5;
+		break;
+	}
+
+	// Prepare control register.
+	// Common for all modes.
+	instance->single_config[channel].ctrl =
+	    ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO;
+
+	switch (trig_type) {
+	case ME_TRIG_TYPE_SW:
+		// Nothing to set.
+		break;
+
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		instance->single_config[channel].ctrl |=
+		    ME4600_AI_CTRL_BIT_EX_TRIG_ANALOG;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		instance->single_config[channel].ctrl |=
+		    ME4600_AI_CTRL_BIT_EX_TRIG;
+		break;
+	}
+
+	switch (trig_edge) {
+	case ME_TRIG_EDGE_RISING:
+		// Nothing to set.
+		break;
+
+	case ME_TRIG_EDGE_ANY:
+		instance->single_config[channel].ctrl |=
+		    ME4600_AI_CTRL_BIT_EX_TRIG_BOTH;
+
+	case ME_TRIG_EDGE_FALLING:
+		instance->single_config[channel].ctrl |=
+		    ME4600_AI_CTRL_BIT_EX_TRIG_FALLING;
+		break;
+	}
+
+	// Enable this channel
+	instance->single_config[channel].status = ME_SINGLE_CHANNEL_CONFIGURED;
+
+	// Copy this settings to other outputs.
+	if (flags == ME_IO_SINGLE_CONFIG_CONTINUE) {
+		for (i = channel + 1; i < instance->channels; i++) {
+			instance->single_config[i].ctrl =
+			    instance->single_config[channel].ctrl;
+			instance->single_config[i].entry =
+			    instance->single_config[channel].entry;
+			instance->single_config[i].status =
+			    ME_SINGLE_CHANNEL_CONFIGURED;
+		}
+	}
+
+	instance->status = ai_status_single_configured;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	volatile uint32_t tmp;
+	volatile uint32_t val;
+	unsigned long cpu_flags;
+	int err = ME_ERRNO_SUCCESS;
+
+	unsigned long j;
+	unsigned long delay = 0;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (instance->status != ai_status_single_configured) {
+		PERROR("Subdevice not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	if ((channel > instance->channels) || (channel < 0)) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (instance->single_config[channel].status !=
+	    ME_SINGLE_CHANNEL_CONFIGURED) {
+		PERROR("Channel is not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	if (inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM) {
+		PERROR("Subdevice is busy.\n");
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	// Cancel control task
+	PDEBUG("Cancel control task.\n");
+	instance->ai_control_task_flag = 0;
+	cancel_delayed_work(&instance->ai_control_task);
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	// Mark that StreamConfig is removed.
+	instance->chan_list_len = 0;
+
+	spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+	/// @note Imprtant: Preserve EXT IRQ settings.
+	tmp = inl(instance->ctrl_reg);
+	// Clear FIFOs and dissable interrupts
+	tmp &=
+	    ~(ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO);
+
+	tmp &=
+	    ~(ME4600_AI_CTRL_BIT_SC_IRQ | ME4600_AI_CTRL_BIT_HF_IRQ |
+	      ME4600_AI_CTRL_BIT_LE_IRQ);
+	tmp |=
+	    ME4600_AI_CTRL_BIT_SC_IRQ_RESET | ME4600_AI_CTRL_BIT_HF_IRQ_RESET |
+	    ME4600_AI_CTRL_BIT_LE_IRQ_RESET;
+
+	tmp |= ME4600_AI_CTRL_BIT_IMMEDIATE_STOP;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	outl(0, instance->scan_pre_timer_low_reg);
+	PDEBUG_REG("scan_pre_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_pre_timer_high_reg);
+	PDEBUG_REG("scan_pre_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_high_reg - instance->reg_base, 0);
+	outl(0, instance->scan_timer_low_reg);
+	PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_timer_high_reg);
+	PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_high_reg - instance->reg_base, 0);
+	outl(65, instance->chan_timer_reg);
+	PDEBUG_REG("chan_timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->chan_timer_reg - instance->reg_base, 65);
+	outl(65, instance->chan_pre_timer_reg);
+	PDEBUG_REG("chan_pre_timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->chan_pre_timer_reg - instance->reg_base, 65);
+
+	//Reactive FIFOs. Enable work.
+	tmp |= ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	outl(instance->single_config[channel].entry,
+	     instance->channel_list_reg);
+	PDEBUG_REG("channel_list_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->channel_list_reg - instance->reg_base,
+		   instance->single_config[channel].entry);
+
+	// Preserve EXT IRQ settings.
+	tmp &= (ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET);
+	outl(instance->single_config[channel].ctrl | tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base,
+		   instance->single_config[channel].ctrl | tmp);
+
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+	if (!(instance->single_config[channel].ctrl & ME4600_AI_CTRL_BIT_EX_TRIG)) {	// Software start
+		inl(instance->start_reg);
+		PDEBUG_REG("start_reg inl(0x%lX+0x%lX)\n", instance->reg_base,
+			   instance->start_reg - instance->reg_base);
+
+		delay = 2;
+	}
+
+	j = jiffies;
+
+	while (!(inl(instance->status_reg) & ME4600_AI_STATUS_BIT_EF_DATA)) {
+		if (delay && ((jiffies - j) >= delay)) {
+			if (!(instance->single_config[channel].ctrl & ME4600_AI_CTRL_BIT_EX_TRIG)) {	// Software start.
+				PERROR("Value not available after wait.\n");
+				err = ME_ERRNO_INTERNAL;
+			} else {	// External start.
+				PERROR("Timeout reached.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+			break;
+		}
+		// Wait
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+
+		if (signal_pending(current)) {
+			PERROR
+			    ("Wait on external trigger interrupted by signal.\n");
+			err = ME_ERRNO_SIGNAL;
+			break;
+		}
+
+		if (instance->status != ai_status_single_configured) {
+			PERROR("Wait interrupted by reset.\n");
+			err = ME_ERRNO_CANCELLED;
+			break;
+		}
+	}
+
+	// Read value.
+	if (!err) {
+		val = inl(instance->data_reg) ^ 0x8000;
+		PDEBUG_REG("data_reg inl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->data_reg - instance->reg_base, val);
+		*value = val & ME4600_AI_MAX_DATA;
+	} else {
+		*value = 0xFFFFFFFF;
+	}
+
+	// Restore settings.
+	spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+	tmp = inl(instance->ctrl_reg);
+	// Clear FIFOs and dissable interrupts.
+	tmp &=
+	    ~(ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO);
+	tmp |= ME4600_AI_CTRL_BIT_SC_IRQ | ME4600_AI_CTRL_BIT_HF_IRQ;
+	tmp |=
+	    ME4600_AI_CTRL_BIT_SC_IRQ_RESET | ME4600_AI_CTRL_BIT_HF_IRQ_RESET |
+	    ME4600_AI_CTRL_BIT_LE_IRQ_RESET | ME4600_AI_CTRL_BIT_IMMEDIATE_STOP;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	int i;			// internal multipurpose variable
+	unsigned long long data_required;
+
+	volatile uint32_t entry;
+	volatile uint32_t ctrl = ME4600_AI_CTRL_BIT_IMMEDIATE_STOP;
+	volatile uint32_t tmp;	// use when current copy of register's value needed
+	unsigned long cpu_flags;
+
+	uint64_t acq_ticks;
+	uint64_t scan_ticks;
+	uint64_t conv_ticks;
+	unsigned int acq_start_ticks_low = trigger->iAcqStartTicksLow;
+	unsigned int acq_start_ticks_high = trigger->iAcqStartTicksHigh;
+	unsigned int scan_start_ticks_low = trigger->iScanStartTicksLow;
+	unsigned int scan_start_ticks_high = trigger->iScanStartTicksHigh;
+	unsigned int conv_start_ticks_low = trigger->iConvStartTicksLow;
+	unsigned int conv_start_ticks_high = trigger->iConvStartTicksHigh;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    // Convert ticks to 64 bit long values
+	    acq_ticks =
+	    (uint64_t) acq_start_ticks_low +
+	    ((uint64_t) acq_start_ticks_high << 32);
+	scan_ticks =
+	    (uint64_t) scan_start_ticks_low +
+	    ((uint64_t) scan_start_ticks_high << 32);
+	conv_ticks =
+	    (uint64_t) conv_start_ticks_low +
+	    ((uint64_t) conv_start_ticks_high << 32);
+
+	// Check settings - begin
+	switch (trigger->iAcqStartTrigType) {
+	case ME_TRIG_TYPE_SW:
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		break;
+
+	default:
+		PERROR("Invalid acquisition start trigger type specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_TYPE;
+		goto ERROR;
+		break;
+	}
+
+	if ((trigger->iAcqStartTrigType == ME_TRIG_TYPE_SW)
+	    && (trigger->iAcqStartTrigEdge != ME_TRIG_EDGE_NONE)) {
+		PERROR("Invalid acquisition start trigger edge specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+		goto ERROR;
+	}
+
+	if (trigger->iAcqStartTrigType != ME_TRIG_TYPE_SW) {
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_RISING:
+		case ME_TRIG_EDGE_FALLING:
+		case ME_TRIG_EDGE_ANY:
+			break;
+
+		default:
+			PERROR
+			    ("Invalid acquisition start trigger edge specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+			goto ERROR;
+			break;
+		}
+	}
+
+	if (trigger->iAcqStartTrigChan != ME_TRIG_CHAN_DEFAULT) {
+		PERROR
+		    ("Invalid acquisition start trigger channel specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_CHAN;
+		goto ERROR;
+	}
+
+	if ((acq_ticks < ME4600_AI_MIN_ACQ_TICKS)
+	    || (acq_ticks > ME4600_AI_MAX_ACQ_TICKS)) {
+		PERROR
+		    ("Invalid acquisition start trigger argument specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_ARG;
+		goto ERROR;
+	}
+
+	switch (trigger->iScanStartTrigType) {
+
+	case ME_TRIG_TYPE_TIMER:
+		if ((scan_ticks < ME4600_AI_MIN_SCAN_TICKS)
+		    || (scan_ticks > ME4600_AI_MAX_SCAN_TICKS)
+		    || (scan_ticks < count * conv_ticks)
+		    ) {
+			PERROR("Invalid scan start argument specified.\n");
+			err = ME_ERRNO_INVALID_SCAN_START_ARG;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		if (trigger->iAcqStartTrigType != ME_TRIG_TYPE_EXT_DIGITAL) {
+			PERROR
+			    ("Invalid scan start trigger type specified (Acq is HW digital)\n");
+			err = ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		if (trigger->iAcqStartTrigType != ME_TRIG_TYPE_EXT_ANALOG) {
+			PERROR
+			    ("Invalid scan start trigger type specified (Acq is HW analog)\n");
+			err = ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_FOLLOW:
+		break;
+
+	default:
+		PERROR("Invalid scan start trigger type specified.\n");
+		err = ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+		goto ERROR;
+		break;
+	}
+
+	switch (trigger->iConvStartTrigType) {
+
+	case ME_TRIG_TYPE_TIMER:
+		if ((conv_ticks < ME4600_AI_MIN_CHAN_TICKS)
+		    || (conv_ticks > ME4600_AI_MAX_CHAN_TICKS)) {
+			PERROR
+			    ("Invalid conv start trigger argument specified.\n");
+			err = ME_ERRNO_INVALID_CONV_START_ARG;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		if ((trigger->iScanStartTrigType != ME_TRIG_TYPE_FOLLOW)
+		    || (trigger->iAcqStartTrigType !=
+			ME_TRIG_TYPE_EXT_DIGITAL)) {
+			PERROR("Invalid conv start trigger type specified.\n");
+			err = ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		if ((trigger->iScanStartTrigType != ME_TRIG_TYPE_FOLLOW)
+		    || (trigger->iAcqStartTrigType !=
+			ME_TRIG_TYPE_EXT_ANALOG)) {
+			PERROR("Invalid conv start trigger type specified.\n");
+			err = ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	default:
+		PERROR("Invalid conv start trigger type specified.\n");
+		err = ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+		goto ERROR;
+
+		break;
+	}
+/**
+* Aceptable settings:
+* iScanStopTrigType		:	iAcqStopTrigType
+*
+* ME_TRIG_TYPE_NONE		:	ME_TRIG_TYPE_NONE	-> infinite count with manual stop
+* ME_TRIG_TYPE_NONE		:	ME_TRIG_TYPE_COUNT	-> stop after getting iScanStopCount list of values (iScanStopCount * count)
+* ME_TRIG_TYPE_COUNT	:	ME_TRIG_TYPE_FOLLOW	-> stop after getting iAcqStopCount values (it can stops in midle of the list)
+*/
+	switch (trigger->iScanStopTrigType) {
+
+	case ME_TRIG_TYPE_NONE:
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (trigger->iScanStopCount <= 0) {
+			PERROR("Invalid scan stop argument specified.\n");
+			err = ME_ERRNO_INVALID_SCAN_STOP_ARG;
+			goto ERROR;
+		}
+		break;
+
+	default:
+		PERROR("Invalid scan stop trigger type specified.\n");
+		err = ME_ERRNO_INVALID_SCAN_STOP_TRIG_TYPE;
+		goto ERROR;
+		break;
+	}
+
+	switch (trigger->iAcqStopTrigType) {
+
+	case ME_TRIG_TYPE_NONE:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_FOLLOW:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_COUNT) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+
+		if (trigger->iAcqStopCount <= 0) {
+			PERROR
+			    ("Invalid acquisition or scan stop argument specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_ARG;
+			goto ERROR;
+		}
+		break;
+
+	default:
+		PERROR("Invalid acq stop trigger type specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		goto ERROR;
+		break;
+	}
+
+	if ((count <= 0) || (count > ME4600_AI_LIST_COUNT)) {
+		PERROR("Invalid channel list count specified.\n");
+		err = ME_ERRNO_INVALID_CONFIG_LIST_COUNT;
+		goto ERROR;
+	}
+///This is general limitation
+//      if (fifo_irq_threshold < 0 || fifo_irq_threshold >= ME4600_AI_CIRC_BUF_COUNT)
+///This is limitation from Windows. I use it for compatibility.
+	if (fifo_irq_threshold < 0
+	    || fifo_irq_threshold >= ME4600_AI_FIFO_COUNT) {
+		PERROR("Invalid fifo irq threshold specified.\n");
+		err = ME_ERRNO_INVALID_FIFO_IRQ_THRESHOLD;
+		goto ERROR;
+	}
+
+	if ((config_list[0].iRef == ME_REF_AI_DIFFERENTIAL)
+	    && (instance->channels == 16)) {
+		PERROR
+		    ("Differential reference is not available on this subdevice.\n");
+		err = ME_ERRNO_INVALID_REF;
+		goto ERROR;
+	}
+
+	if (flags & ME_IO_STREAM_CONFIG_SAMPLE_AND_HOLD) {
+		if (!instance->sh) {
+			PERROR
+			    ("Sample and hold is not available for this board.\n");
+			err = ME_ERRNO_INVALID_FLAGS;
+			goto ERROR;
+		}
+		if (config_list[0].iRef == ME_REF_AI_DIFFERENTIAL) {
+			PERROR
+			    ("Sample and hold is not available in differential mode.\n");
+			err = ME_ERRNO_INVALID_FLAGS;
+			goto ERROR;
+		}
+	}
+
+	for (i = 0; i < count; i++) {
+		if ((config_list[i].iStreamConfig < 0)
+		    || (config_list[i].iStreamConfig >= instance->ranges_len)) {
+			PERROR("Invalid stream config specified.\n");
+			err = ME_ERRNO_INVALID_STREAM_CONFIG;
+			goto ERROR;
+		}
+
+		if ((config_list[i].iRef != ME_REF_AI_GROUND)
+		    && (config_list[i].iRef != ME_REF_AI_DIFFERENTIAL)) {
+			PERROR("Invalid references in the list. Ref=0x%x\n",
+			       config_list[i].iRef);
+			err = ME_ERRNO_INVALID_REF;
+			goto ERROR;
+		}
+
+		if (config_list[i].iStreamConfig % 2) {	// StreamConfig: 1 or 3
+			if (config_list[i].iRef == ME_REF_AI_DIFFERENTIAL) {
+				PERROR
+				    ("Only bipolar modes support differential measurement.\n");
+				err = ME_ERRNO_INVALID_REF;
+				goto ERROR;
+			}
+		}
+
+		if (config_list[i].iRef != config_list[0].iRef) {
+			PERROR
+			    ("Not all references in the configuration list are equal. Ref[0]=0x%x Ref[%d]=0x%x\n",
+			     config_list[0].iRef, i, config_list[i].iRef);
+			err = ME_ERRNO_INVALID_REF;
+			goto ERROR;
+		}
+
+		if ((config_list[i].iRef == ME_REF_AI_DIFFERENTIAL)
+		    && (config_list[i].iChannel >= 16)) {
+			PERROR("Channel not available in differential mode.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+			goto ERROR;
+		}
+
+		if ((config_list[i].iChannel < 0)
+		    || (config_list[i].iChannel >= instance->channels)) {
+			PERROR("Invalid channel number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+			goto ERROR;
+		}
+	}
+
+	// Check settings - end
+
+	//Cancel control task
+	PDEBUG("Cancel control task.\n");
+	instance->ai_control_task_flag = 0;
+	cancel_delayed_work(&instance->ai_control_task);
+
+	// Work around from Keith Hartley - begin
+	if (trigger->iScanStartTrigType == ME_TRIG_TYPE_TIMER) {
+		if (count == 1) {
+			// The hardware does not work properly with a non-zero scan time
+			// if there is only ONE channel in the channel list. In this case
+			// we must set the scan time to zero and use the channel time.
+
+			conv_ticks = scan_ticks;
+			trigger->iScanStartTrigType = ME_TRIG_TYPE_FOLLOW;
+		} else if (scan_ticks == count * conv_ticks) {
+			// Another hardware problem. If the number of scan ticks is
+			// exactly equal to the number of channel ticks multiplied by
+			// the number of channels then the sampling rate is reduced
+			// by half.
+			trigger->iScanStartTrigType = ME_TRIG_TYPE_FOLLOW;
+		}
+	}
+	// Work around from Keith Hartley - end
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	if (inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM) {
+		PERROR("Subdevice is busy.\n");
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		ME_SUBDEVICE_EXIT;
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	instance->status = ai_status_none;
+	spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+	// Stop all actions. Block all interrupts. Clear (disable) FIFOs.
+	ctrl =
+	    ME4600_AI_CTRL_BIT_LE_IRQ_RESET | ME4600_AI_CTRL_BIT_HF_IRQ_RESET |
+	    ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+
+	tmp = inl(instance->ctrl_reg);
+	// Preserve EXT IRQ and OFFSET settings. Clean other bits.
+	tmp &=
+	    (ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET |
+	     ME4600_AI_CTRL_BIT_FULLSCALE | ME4600_AI_CTRL_BIT_OFFSET);
+
+	// Send it to register.
+	outl(tmp | ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp | ctrl);
+
+	// Enable channel fifo -> data fifo in stream_start().
+	ctrl |= ME4600_AI_CTRL_BIT_CHANNEL_FIFO;
+	outl(tmp | ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp | ctrl);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+	// Write the channel list
+	for (i = 0; i < count; i++) {
+		entry = config_list[i].iChannel;
+
+		switch (config_list[i].iStreamConfig) {
+		case 0:	//BIPOLAR 10V
+/*
+				// ME4600_AI_LIST_RANGE_BIPOLAR_10 = 0x0000
+				// 'entry |= ME4600_AI_LIST_RANGE_BIPOLAR_10' <== Do nothing. Removed.
+				entry |= ME4600_AI_LIST_RANGE_BIPOLAR_10;
+*/
+			break;
+		case 1:	//UNIPOLAR 10V
+			entry |= ME4600_AI_LIST_RANGE_UNIPOLAR_10;
+			break;
+		case 2:	//BIPOLAR 2.5V
+			entry |= ME4600_AI_LIST_RANGE_BIPOLAR_2_5;
+			break;
+		case 3:	//UNIPOLAR 2.5V
+			entry |= ME4600_AI_LIST_RANGE_UNIPOLAR_2_5;
+			break;
+		default:
+			PERROR_CRITICAL("UNCHECK ERROR in config_list!\n");
+			PERROR_CRITICAL
+			    ("WRONG range\nPosition:%d Range:0x%04X\n", i,
+			     config_list[i].iStreamConfig);
+			goto VERIFY_ERROR;
+			break;
+		}
+
+		switch (config_list[i].iRef) {
+		case ME_REF_AI_GROUND:	//SINGLE ENDED
+/*
+				// ME4600_AI_LIST_INPUT_SINGLE_ENDED = 0x0000
+				// 'entry |= ME4600_AI_LIST_INPUT_SINGLE_ENDED' ==> Do nothing. Removed.
+				entry |= ME4600_AI_LIST_INPUT_SINGLE_ENDED;
+*/ break;
+		case ME_REF_AI_DIFFERENTIAL:	//DIFFERENTIAL
+			entry |= ME4600_AI_LIST_INPUT_DIFFERENTIAL;
+			break;
+		default:
+			PERROR_CRITICAL("UNCHECK ERROR in config_list!\n");
+			PERROR_CRITICAL
+			    ("WRONG reference\nPosition:%d Reference:0x%04X\n",
+			     i, config_list[i].iRef);
+			goto VERIFY_ERROR;
+			break;
+		}
+
+		//Add last entry flag
+		if (i == (count - 1)) {
+			entry |= ME4600_AI_LIST_LAST_ENTRY;
+		}
+
+		outl(entry, instance->channel_list_reg);
+		PDEBUG_REG("channel_list_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->channel_list_reg - instance->reg_base,
+			   entry);
+	}
+
+	// Set triggering registers
+	--acq_ticks;
+	outl(acq_ticks, instance->chan_pre_timer_reg);
+	PDEBUG_REG("chan_pre_timer_reg outl(0x%lX+0x%lX)=0x%llX\n",
+		   instance->reg_base,
+		   instance->chan_pre_timer_reg - instance->reg_base,
+		   acq_ticks);
+	outl(acq_ticks, instance->scan_pre_timer_low_reg);
+	PDEBUG_REG("scan_pre_timer_low_reg outl(0x%lX+0x%lX)=0x%llX\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_low_reg - instance->reg_base,
+		   acq_ticks & 0xFFFFFFFF);
+	outl((acq_ticks >> 32), instance->scan_pre_timer_high_reg);
+	PDEBUG_REG("scan_pre_timer_high_reg outl(0x%lX+0x%lX)=0x%llX\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_high_reg - instance->reg_base,
+		   (acq_ticks >> 32) & 0xFFFFFFFF);
+
+	// Set triggers
+	switch (trigger->iAcqStartTrigType) {
+		// Internal
+	case ME_TRIG_TYPE_SW:
+		// Nothing to set.
+		break;
+
+		// External
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		ctrl |= ME4600_AI_CTRL_BIT_EX_TRIG_ANALOG;
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		ctrl |= ME4600_AI_CTRL_BIT_EX_TRIG;
+
+		// External trigger needs edge's definition
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_RISING:
+			// Nothing to set.
+			break;
+
+		case ME_TRIG_EDGE_FALLING:
+			ctrl |= ME4600_AI_CTRL_BIT_EX_TRIG_FALLING;
+			break;
+
+		case ME_TRIG_EDGE_ANY:
+			ctrl |=
+			    ME4600_AI_CTRL_BIT_EX_TRIG_FALLING |
+			    ME4600_AI_CTRL_BIT_EX_TRIG_BOTH;
+			break;
+
+		default:
+			PERROR_CRITICAL
+			    ("UNCHECK TRIGGER EDGE in triggers structure!\n");
+			PERROR_CRITICAL
+			    ("WRONG acquisition start trigger:0x%04X.\n",
+			     trigger->iAcqStartTrigEdge);
+			err = ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+			goto VERIFY_ERROR;
+			break;
+		}
+		break;
+
+	default:
+		PERROR_CRITICAL("UNCHECK TRIGGER in triggers structure!\n");
+		PERROR_CRITICAL("WRONG acquisition start trigger:0x%04X.\n",
+				trigger->iAcqStartTrigType);
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_TYPE;
+		goto VERIFY_ERROR;
+		break;
+	}
+
+	switch (trigger->iScanStartTrigType) {
+	case ME_TRIG_TYPE_TIMER:
+		--scan_ticks;
+		outl(scan_ticks, instance->scan_timer_low_reg);
+		PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%llX\n",
+			   instance->reg_base,
+			   instance->scan_timer_low_reg - instance->reg_base,
+			   scan_ticks & 0xFFFFFFFF);
+		outl((scan_ticks >> 32), instance->scan_timer_high_reg);
+		PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%llX\n",
+			   instance->reg_base,
+			   instance->scan_timer_high_reg - instance->reg_base,
+			   (scan_ticks >> 32) & 0xFFFFFFFF);
+
+		if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_SW) {
+			ctrl |= ME4600_AI_CTRL_BIT_MODE_0;
+		} else {
+			ctrl |= ME4600_AI_CTRL_BIT_MODE_1;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		outl(0, instance->scan_timer_low_reg);
+		PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->scan_timer_low_reg - instance->reg_base,
+			   0);
+		outl(0, instance->scan_timer_high_reg);
+		PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->scan_timer_high_reg - instance->reg_base,
+			   0);
+		ctrl |= ME4600_AI_CTRL_BIT_MODE_2;
+		break;
+
+	case ME_TRIG_TYPE_FOLLOW:
+		outl(0, instance->scan_timer_low_reg);
+		PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->scan_timer_low_reg - instance->reg_base,
+			   0);
+		outl(0, instance->scan_timer_high_reg);
+		PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->scan_timer_high_reg - instance->reg_base,
+			   0);
+
+		if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_SW) {
+			ctrl |= ME4600_AI_CTRL_BIT_MODE_0;
+		} else {
+			ctrl |= ME4600_AI_CTRL_BIT_MODE_1;
+		}
+		break;
+
+	default:
+		PERROR_CRITICAL("UNCHECK TRIGGER in triggers structure!\n");
+		PERROR_CRITICAL("WRONG scan start trigger:0x%04X.\n",
+				trigger->iScanStartTrigType);
+		err = ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+		goto VERIFY_ERROR;
+		break;
+	}
+
+	switch (trigger->iConvStartTrigType) {
+
+	case ME_TRIG_TYPE_TIMER:
+		--conv_ticks;
+		outl(conv_ticks, instance->chan_timer_reg);
+		PDEBUG_REG("chan_timer_reg outl(0x%lX+0x%lX)=0x%llX\n",
+			   instance->reg_base,
+			   instance->chan_timer_reg - instance->reg_base,
+			   conv_ticks);
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+	case ME_TRIG_TYPE_EXT_ANALOG:
+		outl(0, instance->chan_timer_reg);
+		PDEBUG_REG("chan_timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->chan_timer_reg - instance->reg_base, 0);
+		ctrl |= ME4600_AI_CTRL_BIT_MODE_0 | ME4600_AI_CTRL_BIT_MODE_1;
+		break;
+
+	default:
+		PERROR_CRITICAL("UNCHECK TRIGGER in triggers structure!\n");
+		PERROR_CRITICAL("WRONG conv start trigger:0x%04X.\n",
+				trigger->iConvStartTrigType);
+		err = ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+		goto VERIFY_ERROR;
+
+		break;
+	}
+
+	//Sample & Hold feature
+	if (flags & ME_IO_STREAM_CONFIG_SAMPLE_AND_HOLD) {
+		if (instance->sh) {
+			ctrl |= ME4600_AI_CTRL_BIT_SAMPLE_HOLD;
+		} else {
+			PERROR_CRITICAL("UNCHECK S&H feature!\n");
+			err = ME_ERRNO_INVALID_FLAGS;
+			goto VERIFY_ERROR;
+		}
+	}
+	//Enable IRQs sources but leave latches blocked.
+	ctrl |= (ME4600_AI_CTRL_BIT_HF_IRQ | ME4600_AI_CTRL_BIT_SC_IRQ | ME4600_AI_CTRL_BIT_LE_IRQ);	//The last IRQ source (ME4600_AI_CTRL_BIT_LE_IRQ) is unused!
+
+	//Everything is good. Finalize
+	spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+	tmp = inl(instance->ctrl_reg);
+
+	//Preserve EXT IRQ and OFFSET settings. Clean other bits.
+	tmp &=
+	    (ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET |
+	     ME4600_AI_CTRL_BIT_FULLSCALE | ME4600_AI_CTRL_BIT_OFFSET);
+
+	// write the control word
+	outl(ctrl | tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl | tmp);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+	//Set the global parameters end exit.
+	instance->chan_list_len = count;
+	instance->fifo_irq_threshold = fifo_irq_threshold;
+
+	if (trigger->iAcqStopTrigType == ME_TRIG_TYPE_COUNT) {
+		data_required =
+		    (unsigned long long)trigger->iAcqStopCount *
+		    (unsigned long long)count;
+		if (data_required > UINT_MAX)
+			data_required = UINT_MAX;
+		instance->data_required = (unsigned int)data_required;
+	} else if (trigger->iScanStopTrigType == ME_TRIG_TYPE_COUNT)
+		instance->data_required =
+		    (unsigned long long)trigger->iScanStopCount;
+	else
+		instance->data_required = 0;
+
+	// Mark subdevice as configured to work in stream mode.
+	instance->status = ai_status_stream_configured;
+
+	// Deinit single config. Set all entries to NOT_CONFIGURED.
+	for (i = 0; i < instance->channels; i++) {
+		instance->single_config[i].status =
+		    ME_SINGLE_CHANNEL_NOT_CONFIGURED;
+	}
+
+      VERIFY_ERROR:		// Error in code. Wrong setting check. This should never ever happend!
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+      ERROR:			// Error in settings.
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long t;
+	unsigned long j;
+	int volatile head;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	} else {		// Max time.
+		t = LONG_MAX;
+	}
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	j = jiffies;
+
+	while (1) {
+		// Only runing device can generate break.
+		head = instance->circ_buf.head;
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((head !=
+						   instance->circ_buf.head)
+						  ||
+						  ((instance->status <=
+						    ai_status_stream_run_wait)
+						   && (instance->status >=
+						       ai_status_stream_end_wait))),
+						 t);
+
+		if (head != instance->circ_buf.head) {	// New data in buffer.
+			break;
+		} else if (instance->status == ai_status_stream_end) {	// End of work.
+			break;
+		} else if (instance->status == ai_status_stream_fifo_error) {
+			err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+			break;
+		} else if (instance->status == ai_status_stream_buffer_error) {
+			err = ME_ERRNO_RING_BUFFER_OVERFLOW;
+			break;
+		} else if (instance->status == ai_status_stream_error) {
+			err = ME_ERRNO_INTERNAL;
+			break;
+		} else if ((jiffies - j) >= t) {
+			PERROR("Wait on values timed out.\n");
+			err = ME_ERRNO_TIMEOUT;
+			break;
+		} else if (signal_pending(current)) {
+			PERROR("Wait on values interrupted from signal.\n");
+			err = ME_ERRNO_SIGNAL;
+			break;
+		}
+		// Correct timeout.
+		t -= jiffies - j;
+	}
+
+	*count = me_circ_buf_values(&instance->circ_buf);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int inline me4600_ai_io_stream_read_get_value(me4600_ai_subdevice_t *
+						     instance, int *values,
+						     const int count,
+						     const int flags)
+{
+	int n;
+	int i;
+	uint32_t value;
+
+	///Checking how many datas can be copied.
+	n = me_circ_buf_values(&instance->circ_buf);
+	if (n <= 0)
+		return 0;
+
+	if (n > count)
+		n = count;
+
+	if (flags & ME_IO_STREAM_READ_FRAMES) {
+		if (n < instance->chan_list_len)	//Not enough data!
+			return 0;
+		n -= n % instance->chan_list_len;
+	}
+
+	for (i = 0; i < n; i++) {
+		value = *(instance->circ_buf.buf + instance->circ_buf.tail);
+		if (put_user(value, values + i)) {
+			PERROR("Cannot copy new values to user.\n");
+			return -ME_ERRNO_INTERNAL;
+		}
+		instance->circ_buf.tail++;
+		instance->circ_buf.tail &= instance->circ_buf.mask;
+	}
+	return n;
+}
+
+static int me4600_ai_io_stream_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int read_mode,
+				    int *values, int *count, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	int ret;
+
+	int c = *count;
+	int min = c;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (flags & ~ME_IO_STREAM_READ_FRAMES) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!values || !count) {
+		PERROR("Request has invalid pointer.\n");
+		return ME_ERRNO_INVALID_POINTER;
+	}
+
+	if (c < 0) {
+		PERROR("Request has invalid value's counter.\n");
+		return ME_ERRNO_INVALID_VALUE_COUNT;
+	}
+
+	if ((read_mode != ME_READ_MODE_BLOCKING)
+	    && (read_mode != ME_READ_MODE_NONBLOCKING)) {
+		PERROR("Invalid read mode specified.\n");
+		return ME_ERRNO_INVALID_READ_MODE;
+	}
+
+	if (c == 0) {		//You get what you want! Nothing more or less.
+		return ME_ERRNO_SUCCESS;
+	}
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+	ME_SUBDEVICE_ENTER;
+
+	//Check if subdevice is configured.
+	if (instance->chan_list_len <= 0) {
+		PERROR("Subdevice wasn't configured.\n");
+		ME_SUBDEVICE_EXIT;
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	if (flags & ME_IO_STREAM_READ_FRAMES) {
+		if (c < instance->chan_list_len) {	//Not enough data requested.
+			PERROR
+			    ("When using FRAME_READ mode minimal size is defined by channel list.\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INVALID_VALUE_COUNT;
+		}
+	}
+
+	if (c > (ME4600_AI_CIRC_BUF_COUNT - instance->chan_list_len)) {	// To return acceptable amount of data when user pass too big value.
+		min = ME4600_AI_CIRC_BUF_COUNT - instance->chan_list_len;
+	}
+
+	if (flags & ME_IO_STREAM_READ_FRAMES) {
+		//Wait for whole list.
+		if (read_mode == ME_READ_MODE_BLOCKING) {
+			min = c - (c % instance->chan_list_len);
+		}
+
+		if (read_mode == ME_READ_MODE_NONBLOCKING) {
+			min = instance->chan_list_len;
+		}
+	}
+
+	if ((inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM)) {	//Working
+		//If blocking mode -> wait for data.
+		if ((me_circ_buf_values(&instance->circ_buf) < min)
+		    && (read_mode == ME_READ_MODE_BLOCKING)) {
+			wait_event_interruptible(instance->wait_queue,
+						 ((me_circ_buf_values
+						   (&instance->circ_buf) >= min)
+						  || !(inl(instance->status_reg)
+						       &
+						       ME4600_AI_STATUS_BIT_FSM)));
+
+			if (signal_pending(current)) {
+				PERROR
+				    ("Wait on values interrupted from signal.\n");
+				err = ME_ERRNO_SIGNAL;
+			}
+		}
+	}
+
+	ret = me4600_ai_io_stream_read_get_value(instance, values, c, flags);
+	if (ret < 0) {
+		err = -ret;
+		*count = 0;
+	} else if (ret == 0) {
+		*count = 0;
+		if (instance->status == ai_status_stream_fifo_error) {
+			err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+			instance->status = ai_status_stream_end;
+		} else if (instance->status == ai_status_stream_buffer_error) {
+			err = ME_ERRNO_RING_BUFFER_OVERFLOW;
+			instance->status = ai_status_stream_end;
+		} else if (instance->status == ai_status_stream_end) {
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+		} else if (instance->status == ai_status_stream_error) {
+			err = ME_ERRNO_INTERNAL;
+		} else if (instance->status == ai_status_none) {
+			PDEBUG("Stream canceled.\n");
+			err = ME_ERRNO_INTERNAL;
+		}
+	} else {
+		*count = ret;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+/** @brief Stop aqusation. Preserve FIFOs.
+*
+* @param instance The subdevice instance (pointer).
+*/
+
+static int ai_stop_immediately(me4600_ai_subdevice_t * instance)
+{
+	unsigned long cpu_flags = 0;
+	volatile uint32_t ctrl;
+	const int timeout = HZ / 10;	//100ms
+	int i;
+
+	for (i = 0; i <= timeout; i++) {
+		spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME4600_AI_CTRL_BIT_STOP;
+		ctrl |=
+		    (ME4600_AI_CTRL_BIT_IMMEDIATE_STOP |
+		     ME4600_AI_CTRL_BIT_HF_IRQ_RESET |
+		     ME4600_AI_CTRL_BIT_SC_IRQ_RESET);
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+		if (!(inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM)) {	// Exit.
+			break;
+		}
+
+		PINFO("Wait for stop: %d\n", i + 1);
+		//Still working!
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	if (i > timeout) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		return ME_ERRNO_INTERNAL;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags = 0;
+	unsigned long ref;
+	unsigned long delay = 0;
+
+	volatile uint32_t tmp;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((start_mode != ME_START_MODE_BLOCKING)
+	    && (start_mode != ME_START_MODE_NONBLOCKING)) {
+		PERROR("Invalid start mode specified.\n");
+		return ME_ERRNO_INVALID_START_MODE;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+
+	tmp = inl(instance->ctrl_reg);
+
+	if ((tmp & ME4600_AI_STATUS_BIT_FSM)) {
+		PERROR("Conversion is already running.\n");
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+		err = ME_ERRNO_SUBDEVICE_BUSY;
+		goto ERROR;
+	}
+
+	if (instance->chan_list_len == 0) {	//Not configured!
+		PERROR("Subdevice is not configured to work in stream mode!\n");
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+		goto ERROR;
+	}
+
+	if (!(tmp & (ME4600_AI_CTRL_BIT_MODE_0 | ME4600_AI_CTRL_BIT_MODE_1 | ME4600_AI_CTRL_BIT_MODE_2))) {	//Mode 0 = single work => no stream config
+		PERROR("Subdevice is configured to work in single mode.\n");
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+		goto ERROR;
+	}
+	//Reset stop bits.
+	tmp |= ME4600_AI_CTRL_BIT_IMMEDIATE_STOP | ME4600_AI_CTRL_BIT_STOP;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	//Start datas' FIFO.
+	tmp |= ME4600_AI_CTRL_BIT_DATA_FIFO;
+	//Free stop bits.
+	tmp &= ~(ME4600_AI_CTRL_BIT_IMMEDIATE_STOP | ME4600_AI_CTRL_BIT_STOP);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+	//Cancel control task
+	PDEBUG("Cancel control task.\n");
+	instance->ai_control_task_flag = 0;
+	cancel_delayed_work(&instance->ai_control_task);
+
+	//Set the starting values.
+	instance->ISM.global_read = 0;
+	instance->ISM.read = 0;
+	//Clear circular buffer
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+
+	//Set everything.
+	ai_data_acquisition_logic(instance);
+
+	//Set status to 'wait for start'
+	instance->status = ai_status_stream_run_wait;
+
+	// Set control task's timeout
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = jiffies;
+
+	//Lets go! Start work
+	inl(instance->start_reg);
+	PDEBUG_REG("start_reg inl(0x%lX+0x%lX)\n", instance->reg_base,
+		   instance->start_reg - instance->reg_base);
+
+	// Schedule control task
+	instance->ai_control_task_flag = 1;
+	queue_delayed_work(instance->me4600_workqueue,
+			   &instance->ai_control_task, 1);
+
+	PDEVELOP("Delay:%ld\n", delay);
+
+	if (start_mode == ME_START_MODE_BLOCKING) {	//Wait for start.
+		ref = jiffies;
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ai_status_stream_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if ((instance->status != ai_status_stream_run)
+		    && (instance->status != ai_status_stream_end)) {
+			PDEBUG("Starting stream canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->status = ai_status_none;
+			ai_stop_isr(instance);
+			err = ME_ERRNO_SIGNAL;
+		} else if ((delay) && ((jiffies - ref) > delay)) {
+			if (instance->status != ai_status_stream_run) {
+				if (instance->status == ai_status_stream_end) {
+					PDEBUG("Timeout reached.\n");
+				} else if ((jiffies - ref) > delay + 1) {
+					PERROR
+					    ("Timeout reached. Not handled by control task!\n");
+					ai_stop_isr(instance);
+					instance->status =
+					    ai_status_stream_error;
+				} else {
+					PERROR
+					    ("Timeout reached. Signal come but status is strange: %d\n",
+					     instance->status);
+					ai_stop_isr(instance);
+					instance->status =
+					    ai_status_stream_error;
+				}
+
+				instance->ai_control_task_flag = 0;
+				cancel_delayed_work(&instance->ai_control_task);
+				err = ME_ERRNO_TIMEOUT;
+			}
+		}
+	}
+#ifdef MEDEBUG_INFO
+	tmp = inl(instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	PINFO("STATUS_BIT_FSM=%s.\n",
+	      (tmp & ME4600_AI_STATUS_BIT_FSM) ? "on" : "off");
+	PINFO("CTRL_BIT_HF_IRQ=%s.\n",
+	      (tmp & ME4600_AI_CTRL_BIT_HF_IRQ) ? "enable" : "disable");
+	PINFO("CTRL_BIT_HF_IRQ_RESET=%s.\n",
+	      (tmp & ME4600_AI_CTRL_BIT_HF_IRQ_RESET) ? "reset" : "work");
+	PINFO("CTRL_BIT_SC_IRQ=%s.\n",
+	      (tmp & ME4600_AI_CTRL_BIT_SC_IRQ) ? "enable" : "disable");
+	PINFO("CTRL_BIT_SC_RELOAD=%s.\n",
+	      (tmp & ME4600_AI_CTRL_BIT_SC_RELOAD) ? "on" : "off");
+	PINFO("CTRL_BIT_SC_IRQ_RESET=%s.\n",
+	      (tmp & ME4600_AI_CTRL_BIT_SC_IRQ_RESET) ? "reset" : "work");
+#endif
+
+      ERROR:
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	switch (instance->status) {
+	case ai_status_single_configured:
+	case ai_status_stream_configured:
+	case ai_status_stream_end:
+	case ai_status_stream_fifo_error:
+	case ai_status_stream_buffer_error:
+	case ai_status_stream_error:
+		*status = ME_STATUS_IDLE;
+		break;
+
+	case ai_status_stream_run_wait:
+	case ai_status_stream_run:
+	case ai_status_stream_end_wait:
+		*status = ME_STATUS_BUSY;
+		break;
+
+	case ai_status_none:
+	default:
+		*status =
+		    (inl(instance->status_reg) & ME4600_AI_STATUS_BIT_FSM) ?
+		    ME_STATUS_BUSY : ME_STATUS_IDLE;
+		break;
+	}
+
+	if ((wait == ME_WAIT_IDLE) && (*status == ME_STATUS_BUSY)) {
+		// Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((instance->status !=
+						   ai_status_stream_run_wait)
+						  && (instance->status !=
+						      ai_status_stream_run)
+						  && (instance->status !=
+						      ai_status_stream_end_wait)),
+						 LONG_MAX);
+
+		if (instance->status != ai_status_stream_end) {
+			PDEBUG("Wait for IDLE canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait for IDLE interrupted.\n");
+			instance->status = ai_status_none;
+			ai_stop_isr(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		*status = ME_STATUS_IDLE;
+	}
+
+	*values = me_circ_buf_values(&instance->circ_buf);
+	PDEBUG("me_circ_buf_values(&instance->circ_buf)=%d.\n", *values);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ai_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags)
+{
+/**
+ @note Stop is implemented only in blocking mode.
+ @note Function return when state machine is stoped.
+*/
+	me4600_ai_subdevice_t *instance;
+	unsigned long cpu_flags;
+	uint32_t ctrl;
+	int ret;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((stop_mode != ME_STOP_MODE_IMMEDIATE)
+	    && (stop_mode != ME_STOP_MODE_LAST_VALUE)) {
+		PERROR("Invalid stop mode specified.\n");
+		return ME_ERRNO_INVALID_STOP_MODE;
+	}
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	// Mark as stopping. => Software stop.
+	instance->status = ai_status_stream_end_wait;
+
+	if (stop_mode == ME_STOP_MODE_IMMEDIATE) {
+		ret = ai_stop_immediately(instance);
+
+		if (ret) {
+			PERROR("FSM is still busy.\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_SUBDEVICE_BUSY;
+		}
+		instance->ai_control_task_flag = 0;
+
+	} else if (stop_mode == ME_STOP_MODE_LAST_VALUE) {
+		// Set stop bit in registry.
+		spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+		ctrl = inl(instance->ctrl_reg);
+		ctrl |= ME4600_AI_CTRL_BIT_STOP;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+
+		// Only runing process will interrupt this call. Events are signaled when status change.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ai_status_stream_end_wait),
+						 LONG_MAX);
+
+		if (instance->status != ai_status_stream_end) {
+			PDEBUG("Stopping stream canceled.\n");
+			ret = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Stopping stream interrupted.\n");
+			instance->status = ai_status_none;
+			ret = ME_ERRNO_SIGNAL;
+		}
+		// End of work.
+		ai_stop_immediately(instance);
+
+	}
+
+	ret = ai_read_data_pooling(instance);
+	if (ret > 0) {		// Everything fine. More datas put to software buffer.
+		instance->status = ai_status_stream_end;
+		ret = ME_ERRNO_SUCCESS;
+		// Signal that we put last data to software buffer.
+		wake_up_interruptible_all(&instance->wait_queue);
+	} else if (ret == 0) {	// Everything fine. No more datas in FIFO.
+		instance->status = ai_status_stream_end;
+		ret = ME_ERRNO_SUCCESS;
+	} else if (ret == -ME_ERRNO_RING_BUFFER_OVERFLOW) {	// Stop is unsuccessful, buffer is overflow.
+		instance->status = ai_status_stream_buffer_error;
+		ret = ME_ERRNO_SUCCESS;
+	} else {		// Stop is unsuccessful
+		instance->status = ai_status_stream_end;
+		ret = -ret;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return ret;
+}
+
+static int me4600_ai_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range)
+{
+	me4600_ai_subdevice_t *instance;
+	int i;
+	int r = -1;
+	int diff = 21E6;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if ((*max - *min) < 0) {
+		PERROR("Invalid minimum and maximum values specified.\n");
+		return ME_ERRNO_INVALID_MIN_MAX;
+	}
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		for (i = 0; i < instance->ranges_len; i++) {
+			if ((instance->ranges[i].min <= *min)
+			    && ((instance->ranges[i].max + 1000) >= *max)) {
+				if ((instance->ranges[i].max -
+				     instance->ranges[i].min) - (*max - *min) <
+				    diff) {
+					r = i;
+					diff =
+					    (instance->ranges[i].max -
+					     instance->ranges[i].min) - (*max -
+									 *min);
+				}
+			}
+		}
+
+		if (r < 0) {
+			PERROR("No matching range found.\n");
+			return ME_ERRNO_NO_RANGE;
+		} else {
+			*min = instance->ranges[r].min;
+			*max = instance->ranges[r].max;
+			*maxdata = ME4600_AI_MAX_DATA;
+			*range = r;
+		}
+	} else {
+		PERROR("Invalid physical unit specified.\n");
+		return ME_ERRNO_INVALID_UNIT;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count)
+{
+	me4600_ai_subdevice_t *instance;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		*count = instance->ranges_len;
+	} else {
+		*count = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata)
+{
+	me4600_ai_subdevice_t *instance;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	if ((range < instance->ranges_len) && (range >= 0)) {
+		*unit = ME_UNIT_VOLT;
+		*min = instance->ranges[range].min;
+		*max = instance->ranges[range].max;
+		*maxdata = ME4600_AI_MAX_DATA;
+	} else {
+		PERROR("Invalid range number specified.\n");
+		return ME_ERRNO_INVALID_RANGE;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks)
+{
+	me4600_ai_subdevice_t *instance;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	switch (timer) {
+
+	case ME_TIMER_ACQ_START:
+		*base_frequency = ME4600_AI_BASE_FREQUENCY;
+		*min_ticks = ME4600_AI_MIN_ACQ_TICKS;
+		*max_ticks = ME4600_AI_MAX_ACQ_TICKS;
+		break;
+
+	case ME_TIMER_SCAN_START:
+		*base_frequency = ME4600_AI_BASE_FREQUENCY;
+		*min_ticks = ME4600_AI_MIN_SCAN_TICKS;
+		*max_ticks = ME4600_AI_MAX_SCAN_TICKS;
+		break;
+
+	case ME_TIMER_CONV_START:
+		*base_frequency = ME4600_AI_BASE_FREQUENCY;
+		*min_ticks = ME4600_AI_MIN_CHAN_TICKS;
+		*max_ticks = ME4600_AI_MAX_CHAN_TICKS;
+		break;
+
+	default:
+		PERROR("Invalid timer specified.(0x%04x)\n", timer);
+
+		return ME_ERRNO_INVALID_TIMER;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	me4600_ai_subdevice_t *instance;
+
+	PDEBUG("executed. idx=0\n");
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+	*number = instance->channels;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed. idx=0\n");
+
+	*type = ME_TYPE_AI;
+	*subtype = ME_SUBTYPE_STREAMING;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed. idx=0\n");
+
+	*caps =
+	    ME_CAPS_AI_TRIG_SYNCHRONOUS | ME_CAPS_AI_FIFO |
+	    ME_CAPS_AI_FIFO_THRESHOLD;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ai_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count)
+{
+	me4600_ai_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me4600_ai_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (count != 1) {
+		PERROR("Invalid capability argument count.\n");
+		return ME_ERRNO_INVALID_CAP_ARG_COUNT;
+	}
+
+	switch (cap) {
+	case ME_CAP_AI_FIFO_SIZE:
+		args[0] = ME4600_AI_FIFO_COUNT;
+		break;
+
+	case ME_CAP_AI_BUFFER_SIZE:
+		args[0] =
+		    (instance->circ_buf.buf) ? ME4600_AI_CIRC_BUF_COUNT : 0;
+		break;
+
+	default:
+		PERROR("Invalid capability.\n");
+		err = ME_ERRNO_INVALID_CAP;
+		args[0] = 0;
+	}
+
+	return err;
+}
+
+void ai_limited_isr(me4600_ai_subdevice_t * instance, const uint32_t irq_status,
+		    const uint32_t ctrl_status)
+{
+	int to_read;
+
+	if (!instance->fifo_irq_threshold) {	//No threshold provided. SC ends work. HF need reseting.
+		if (irq_status & ME4600_IRQ_STATUS_BIT_SC) {
+			if (ai_read_data(instance, instance->ISM.next) != instance->ISM.next) {	//ERROR!
+				PERROR
+				    ("Limited amounts aqusition with TH=0: Circular buffer full!\n");
+				instance->status =
+				    ai_status_stream_buffer_error;
+			} else {
+				instance->status = ai_status_stream_end;
+			}
+			//End of work.
+			ai_stop_isr(instance);
+		} else if (irq_status & ME4600_IRQ_STATUS_BIT_AI_HF) {
+			instance->ISM.global_read += ME4600_AI_FIFO_HALF;
+
+			if (ai_read_data(instance, ME4600_AI_FIFO_HALF) != ME4600_AI_FIFO_HALF) {	//ERROR!
+				PERROR
+				    ("Limited amounts aqusition with TH = 0: Circular buffer full!\n");
+				//End of work.
+				ai_stop_isr(instance);
+				instance->status =
+				    ai_status_stream_buffer_error;
+			} else {
+				//Continue.
+				ai_limited_ISM(instance, irq_status);
+			}
+		}
+		//Signal user.
+		wake_up_interruptible_all(&instance->wait_queue);
+	} else			//if(instance->fifo_irq_threshold)
+	{
+		if (irq_status & ME4600_IRQ_STATUS_BIT_SC) {
+			instance->ISM.read = 0;
+			if ((instance->fifo_irq_threshold < ME4600_AI_FIFO_HALF)
+			    && (!(ctrl_status & ME4600_AI_STATUS_BIT_HF_DATA)))
+			{
+				to_read =
+				    ME4600_AI_FIFO_HALF -
+				    (ME4600_AI_FIFO_HALF %
+				     instance->fifo_irq_threshold);
+				PDEBUG
+				    ("Limited amounts aqusition with TH != 0: Not fast enough data aqusition! correction=%d\n",
+				     to_read);
+			} else {
+				to_read = instance->ISM.next;
+			}
+			instance->ISM.global_read += to_read;
+
+			ai_reschedule_SC(instance);
+
+			if (ai_read_data(instance, to_read) != to_read) {	//ERROR!
+				PERROR
+				    ("Limited amounts aqusition with TH != 0: Circular buffer full!\n");
+				//End of work.
+				ai_stop_isr(instance);
+				instance->status =
+				    ai_status_stream_buffer_error;
+			} else {
+				//Continue.
+				ai_limited_ISM(instance, irq_status);
+			}
+
+			//Signal user.
+			wake_up_interruptible_all(&instance->wait_queue);
+		} else if (irq_status & ME4600_IRQ_STATUS_BIT_AI_HF) {
+			instance->ISM.read += ME4600_AI_FIFO_HALF;
+			instance->ISM.global_read += ME4600_AI_FIFO_HALF;
+
+			if (ai_read_data(instance, ME4600_AI_FIFO_HALF) != ME4600_AI_FIFO_HALF) {	//ERROR!
+				PERROR
+				    ("Limited amounts aqusition with TH != 0: Circular buffer full!\n");
+				ai_stop_isr(instance);
+
+				instance->status =
+				    ai_status_stream_buffer_error;
+				//Signal user.
+				wake_up_interruptible_all(&instance->
+							  wait_queue);
+			} else {
+				//Countinue.
+				ai_limited_ISM(instance, irq_status);
+			}
+		}
+
+		if (instance->ISM.global_read >= instance->data_required) {	//End of work. Next paranoid pice of code: '>=' instead od '==' only to be sure.
+			ai_stop_isr(instance);
+			if (instance->status < ai_status_stream_end) {
+				instance->status = ai_status_stream_end;
+			}
+#ifdef MEDEBUG_ERROR
+			if (instance->ISM.global_read > instance->data_required) {	//This is security check case. This should never ever happend!
+				PERROR
+				    ("Limited amounts aqusition: Read more data than necessary! data_required=%d < read=%d\n",
+				     instance->data_required,
+				     instance->ISM.global_read);
+				//Signal error (warning??).
+				instance->status = ai_status_stream_error;
+			}
+#endif
+		}
+	}
+}
+
+void ai_infinite_isr(me4600_ai_subdevice_t * instance,
+		     const uint32_t irq_status, const uint32_t ctrl_status)
+{
+	int to_read;
+
+	if (irq_status & ME4600_IRQ_STATUS_BIT_SC) {	//next chunck of data -> read fifo
+		//Set new state in ISM.
+		if ((instance->fifo_irq_threshold < ME4600_AI_FIFO_HALF) && (!(ctrl_status & ME4600_AI_STATUS_BIT_HF_DATA))) {	//There is more data than we ecpected. Propably we aren't fast enough. Read as many as possible.
+			if (instance->fifo_irq_threshold) {
+				to_read =
+				    ME4600_AI_FIFO_HALF -
+				    (ME4600_AI_FIFO_HALF %
+				     instance->fifo_irq_threshold);
+				if (to_read > instance->fifo_irq_threshold) {
+					PDEBUG
+					    ("Infinite aqusition: Not fast enough data aqusition! TH != 0: correction=%d\n",
+					     to_read);
+				}
+			} else {	//No threshold specified.
+				to_read = ME4600_AI_FIFO_HALF;
+			}
+		} else {
+			to_read = instance->ISM.next;
+		}
+
+		instance->ISM.read += to_read;
+
+		//Get data
+		if (ai_read_data(instance, to_read) != to_read) {	//ERROR!
+			PERROR("Infinite aqusition: Circular buffer full!\n");
+			ai_stop_isr(instance);
+			instance->status = ai_status_stream_buffer_error;
+		} else {
+			ai_infinite_ISM(instance);
+			instance->ISM.global_read += instance->ISM.read;
+			instance->ISM.read = 0;
+		}
+
+		//Signal data to user
+		wake_up_interruptible_all(&instance->wait_queue);
+	} else if (irq_status & ME4600_IRQ_STATUS_BIT_AI_HF) {	//fifo is half full -> read fifo       Large blocks only!
+		instance->ISM.read += ME4600_AI_FIFO_HALF;
+
+		if (ai_read_data(instance, ME4600_AI_FIFO_HALF) != ME4600_AI_FIFO_HALF) {	//ERROR!
+			PERROR("Infinite aqusition: Circular buffer full!\n");
+			ai_stop_isr(instance);
+			instance->status = ai_status_stream_buffer_error;
+
+			//Signal it.
+			wake_up_interruptible_all(&instance->wait_queue);
+		} else {
+			ai_infinite_ISM(instance);
+		}
+	}
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me4600_ai_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me4600_ai_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{				/// @note This is time critical function!
+	uint32_t irq_status;
+	uint32_t ctrl_status;
+	me4600_ai_subdevice_t *instance = dev_id;
+	//int to_read;
+
+	PDEBUG("executed. idx=0\n");
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inl(instance->irq_status_reg);
+	if (!
+	    (irq_status &
+	     (ME4600_IRQ_STATUS_BIT_AI_HF | ME4600_IRQ_STATUS_BIT_SC))) {
+#ifdef MEDEBUG_INFO
+		if ((irq_status & (ME4600_IRQ_STATUS_BIT_AI_HF | ME4600_IRQ_STATUS_BIT_SC | ME4600_IRQ_STATUS_BIT_LE)) == ME4600_IRQ_STATUS_BIT_LE) {	//This is security check case. LE is unused. This should never ever happend.
+			PINFO
+			    ("%ld Shared interrupt. %s(): irq_status_reg=LE_IRQ\n",
+			     jiffies, __FUNCTION__);
+		} else {
+			PINFO
+			    ("%ld Shared interrupt. %s(): irq_status_reg=0x%04X\n",
+			     jiffies, __FUNCTION__, irq_status);
+		}
+#endif
+		return IRQ_NONE;
+	}
+
+	if (!instance->circ_buf.buf) {	//Security check.
+		PERROR_CRITICAL("CIRCULAR BUFFER NOT EXISTS!\n");
+		ai_stop_isr(instance);
+		return IRQ_HANDLED;
+	}
+	//Get the status register.
+	ctrl_status = inl(instance->status_reg);
+
+#ifdef MEDEBUG_INFO
+	if (irq_status & ME4600_IRQ_STATUS_BIT_AI_HF)
+		PINFO("HF interrupt active\n");
+	if (irq_status & ME4600_IRQ_STATUS_BIT_SC)
+		PINFO("SC interrupt active\n");
+	if (irq_status & ME4600_IRQ_STATUS_BIT_LE)
+		PINFO("LE interrupt active\n");
+#endif
+
+	//This is safety check!
+	if ((irq_status & ME4600_IRQ_STATUS_BIT_AI_HF)
+	    && (ctrl_status & ME4600_AI_STATUS_BIT_HF_DATA)) {
+		PDEBUG("HF interrupt active but FIFO under half\n");
+		//Reset HF interrupt latch.
+		spin_lock(instance->ctrl_reg_lock);
+		outl(ctrl_status | ME4600_AI_CTRL_BIT_HF_IRQ_RESET,
+		     instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   ctrl_status);
+		outl(ctrl_status, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   ctrl_status);
+		spin_unlock(instance->ctrl_reg_lock);
+		return IRQ_HANDLED;
+	}
+#ifdef MEDEBUG_INFO
+	PINFO("STATUS_BIT_FSM=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FSM) ? "on" : "off");
+
+	PINFO("STATUS_BIT_EF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_EF_CHANNEL) ? "not empty" :
+	      "empty");
+	PINFO("STATUS_BIT_HF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_HF_CHANNEL) ? " < HF" :
+	      " > HF");
+	PINFO("STATUS_BIT_FF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FF_CHANNEL) ? "not full" :
+	      "full");
+
+	PINFO("STATUS_BIT_EF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_EF_DATA) ? "not empty" :
+	      "empty");
+	PINFO("STATUS_BIT_HF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_HF_DATA) ? " < HF" : " > HF");
+	PINFO("STATUS_BIT_FF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FF_DATA) ? "not full" :
+	      "full");
+
+	PINFO("CTRL_BIT_HF_IRQ=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_HF_IRQ) ? "enable" : "disable");
+	PINFO("CTRL_BIT_HF_IRQ_RESET=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_HF_IRQ_RESET) ? "reset" :
+	      "work");
+	PINFO("CTRL_BIT_SC_IRQ=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_IRQ) ? "enable" : "disable");
+	PINFO("CTRL_BIT_SC_RELOAD=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_RELOAD) ? "on" : "off");
+	PINFO("CTRL_BIT_SC_IRQ_RESET=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_IRQ_RESET) ? "reset" :
+	      "work");
+#endif
+
+	//Look for overflow error.
+	if (!(ctrl_status & ME4600_AI_STATUS_BIT_FF_DATA)) {
+		//FIFO is full. Read datas and reset all settings.
+		PERROR("FIFO overflow.\n");
+		ai_read_data(instance, ME4600_AI_FIFO_COUNT);
+		ai_stop_isr(instance);
+
+		instance->status = ai_status_stream_fifo_error;
+		//Signal it.
+		wake_up_interruptible_all(&instance->wait_queue);
+
+		return IRQ_HANDLED;
+	}
+
+	if (!instance->data_required) {	//This is infinite aqusition.
+#ifdef MEDEBUG_ERROR
+		if ((irq_status &
+		     (ME4600_IRQ_STATUS_BIT_AI_HF | ME4600_IRQ_STATUS_BIT_SC))
+		    ==
+		    (ME4600_IRQ_STATUS_BIT_AI_HF | ME4600_IRQ_STATUS_BIT_SC)) {
+			///In infinite mode only one interrupt source should be reported!
+			PERROR
+			    ("Error in ISM! Infinite aqusition: HF and SC interrupts active! threshold=%d next=%d ctrl=0x%04X irq_status_reg=0x%04X",
+			     instance->fifo_irq_threshold, instance->ISM.next,
+			     ctrl_status, irq_status);
+		}
+#endif
+
+		ai_infinite_isr(instance, irq_status, ctrl_status);
+
+#ifdef MEDEBUG_INFO
+		ctrl_status = inl(instance->ctrl_reg);
+#endif
+	} else {
+
+		ai_limited_isr(instance, irq_status, ctrl_status);
+		ctrl_status = inl(instance->status_reg);
+		if (!(ctrl_status & (ME4600_AI_STATUS_BIT_HF_DATA | ME4600_AI_CTRL_BIT_HF_IRQ_RESET))) {	//HF active, but we have more than half already => HF will never come
+			PDEBUG
+			    ("MISSED HF. data_required=%d ISM.read=%d ISM.global=%d ISM.next=%d\n",
+			     instance->data_required, instance->ISM.read,
+			     instance->ISM.global_read, instance->ISM.next);
+			ai_limited_isr(instance, ME4600_IRQ_STATUS_BIT_AI_HF,
+				       ctrl_status);
+		}
+	}
+
+#ifdef MEDEBUG_INFO
+	PINFO("STATUS_BIT_FSM=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FSM) ? "on" : "off");
+
+	PINFO("STATUS_BIT_EF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_EF_CHANNEL) ? "not empty" :
+	      "empty");
+	PINFO("STATUS_BIT_HF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_HF_CHANNEL) ? " < HF" :
+	      " > HF");
+	PINFO("STATUS_BIT_FF_CHANNEL=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FF_CHANNEL) ? "not full" :
+	      "full");
+
+	PINFO("STATUS_BIT_EF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_EF_DATA) ? "not empty" :
+	      "empty");
+	PINFO("STATUS_BIT_HF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_HF_DATA) ? " < HF" : " > HF");
+	PINFO("STATUS_BIT_FF_DATA=%s.\n",
+	      (ctrl_status & ME4600_AI_STATUS_BIT_FF_DATA) ? "not full" :
+	      "full");
+
+	PINFO("CTRL_BIT_HF_IRQ_RESET=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_HF_IRQ_RESET) ? "reset" :
+	      "work");
+	PINFO("CTRL_BIT_SC_IRQ=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_IRQ) ? "enable" : "disable");
+	PINFO("CTRL_BIT_SC_RELOAD=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_RELOAD) ? "on" : "off");
+	PINFO("CTRL_BIT_SC_IRQ_RESET=%s.\n",
+	      (ctrl_status & ME4600_AI_CTRL_BIT_SC_IRQ_RESET) ? "reset" :
+	      "work");
+	PINFO("%ld END\n", jiffies);
+#endif
+
+	return IRQ_HANDLED;
+}
+
+/** @brief Stop aqusation of data. Reset interrupts' laches. Clear data's FIFO.
+*
+* @param instance The subdevice instance (pointer).
+*/
+void inline ai_stop_isr(me4600_ai_subdevice_t * instance)
+{				/// @note This is soft time critical function!
+	register uint32_t tmp;
+
+	spin_lock(instance->ctrl_reg_lock);
+	//Stop all. Reset interrupt laches. Reset data FIFO.
+	tmp = inl(instance->ctrl_reg);
+	tmp |=
+	    (ME4600_AI_CTRL_BIT_IMMEDIATE_STOP | ME4600_AI_CTRL_BIT_HF_IRQ_RESET
+	     | ME4600_AI_CTRL_BIT_LE_IRQ_RESET |
+	     ME4600_AI_CTRL_BIT_SC_IRQ_RESET);
+	tmp &= ~ME4600_AI_CTRL_BIT_DATA_FIFO;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->ctrl_reg_lock);
+}
+
+/** @brief Copy data from fifo to circular buffer.
+*
+* @param instance The subdevice instance (pointer).
+* @param count The number of requested data.
+*
+* @return On success: Number of copied values.
+* @return On error: -ME_ERRNO_RING_BUFFER_OVERFLOW.
+*/
+static int inline ai_read_data(me4600_ai_subdevice_t * instance,
+			       const int count)
+{				/// @note This is time critical function!
+	int c = count;
+	int empty_space;
+	int copied = 0;
+	int i, j;
+
+	empty_space = me_circ_buf_space_to_end(&instance->circ_buf);
+	if (empty_space <= 0) {
+		PDEBUG("Circular buffer full.\n");
+		return -ME_ERRNO_RING_BUFFER_OVERFLOW;
+	}
+
+	if (empty_space < c) {	//Copy first part. Max to end of buffer.
+		PDEBUG
+		    ("Try to copy %d values from FIFO to circular buffer (pass 1).\n",
+		     empty_space);
+		for (i = 0; i < empty_space; i++) {
+			*(instance->circ_buf.buf + instance->circ_buf.head) =
+			    (inw(instance->data_reg) ^ 0x8000);
+			instance->circ_buf.head++;
+		}
+		instance->circ_buf.head &= instance->circ_buf.mask;
+		c -= empty_space;
+		copied = empty_space;
+
+		empty_space = me_circ_buf_space_to_end(&instance->circ_buf);
+	}
+
+	if (empty_space > 0) {
+		j = (empty_space < c) ? empty_space : c;
+		PDEBUG
+		    ("Try to copy %d values from FIFO to circular buffer (pass 2).\n",
+		     c);
+		for (i = 0; i < j; i++) {
+			*(instance->circ_buf.buf + instance->circ_buf.head) =
+			    (inw(instance->data_reg) ^ 0x8000);
+			instance->circ_buf.head++;
+		}
+		instance->circ_buf.head &= instance->circ_buf.mask;
+		copied += j;
+	}
+	return copied;
+}
+
+void inline ai_infinite_ISM(me4600_ai_subdevice_t * instance)
+{				/// @note This is time critical function!
+	register volatile uint32_t ctrl_set, ctrl_reset, tmp;
+
+	if (instance->fifo_irq_threshold < ME4600_AI_FIFO_MAX_SC) {	// Only sample counter with reloadnig is working. Reset it.
+		PINFO
+		    ("Only sample counter with reloadnig is working. Reset it.\n");
+		ctrl_set = ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+		ctrl_reset = ~ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+	} else if (instance->fifo_irq_threshold == instance->ISM.read) {	//This is SC interrupt for large block. The whole section is done. Reset SC_IRQ an HF_IRQ and start everything again from beginning.
+		PINFO
+		    ("This is SC interrupt for large block. The whole section is done. Reset SC_IRQ an HF_IRQ and start everything again from beginning.\n");
+		ctrl_set =
+		    ME4600_AI_CTRL_BIT_SC_IRQ_RESET |
+		    ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+		ctrl_reset =
+		    ~(ME4600_AI_CTRL_BIT_SC_IRQ_RESET |
+		      ME4600_AI_CTRL_BIT_HF_IRQ_RESET);
+	} else if (instance->fifo_irq_threshold >= (ME4600_AI_FIFO_MAX_SC + instance->ISM.read)) {	//This is HF interrupt for large block.The next interrupt should be from HF, also. Reset HF.
+		PINFO
+		    ("This is HF interrupt for large block.The next interrupt should be from HF, also. Reset HF.\n");
+		ctrl_set = ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+		ctrl_reset = ~ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+	} else {		//This is HF interrupt for large block.The next interrupt should be from SC. Don't reset HF!
+		PINFO
+		    ("This is HF interrupt for large block.The next interrupt should be from SC. Don't reset HF!\n");
+		ctrl_set = ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+		ctrl_reset = 0xFFFFFFFF;
+	}
+
+	//Reset interrupt latch.
+	spin_lock(instance->ctrl_reg_lock);
+	tmp = inl(instance->ctrl_reg);
+	PINFO("ctrl=0x%x ctrl_set=0x%x ctrl_reset=0x%x\n", tmp, ctrl_set,
+	      ctrl_reset);
+	tmp |= ctrl_set;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	if (ctrl_reset != 0xFFFFFFFF) {
+		outl(tmp & ctrl_reset, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reset outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   tmp & ctrl_reset);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+
+}
+
+void inline ai_limited_ISM(me4600_ai_subdevice_t * instance,
+			   uint32_t irq_status)
+{				/// @note This is time critical function!
+	register volatile uint32_t ctrl_set, ctrl_reset = 0xFFFFFFFF, tmp;
+
+	if (!instance->fifo_irq_threshold) {	//No threshold provided. SC ends work.
+		PINFO("No threshold provided. SC ends work.\n");
+		ctrl_set = ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+		if (instance->data_required > (ME4600_AI_FIFO_COUNT - 1 + instance->ISM.global_read)) {	//HF need reseting.
+			ctrl_reset &= ~ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+		}
+	} else			//if(instance->fifo_irq_threshold)
+	{
+		if (irq_status & ME4600_IRQ_STATUS_BIT_AI_HF) {
+			PINFO("Threshold provided. Clear HF latch.\n");
+			ctrl_set = ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+
+			if (instance->fifo_irq_threshold >= (ME4600_AI_FIFO_MAX_SC + instance->ISM.read)) {	//This is not the last one. HF need reseting.
+				PINFO
+				    ("The next interrupt is HF. HF need be activating.\n");
+				ctrl_reset = ~ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+			}
+		}
+
+		if (irq_status & ME4600_IRQ_STATUS_BIT_SC) {
+			PINFO("Threshold provided. Restart SC.\n");
+			ctrl_set = ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+			ctrl_reset &= ~ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+
+			if (instance->fifo_irq_threshold >= ME4600_AI_FIFO_MAX_SC) {	//This is not the last one. HF need to be activating.
+				PINFO
+				    ("The next interrupt is HF. HF need to be activating.\n");
+				ctrl_reset &= ~ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+			}
+		}
+	}
+
+	//Reset interrupt latch.
+	spin_lock(instance->ctrl_reg_lock);
+	tmp = inl(instance->ctrl_reg);
+	tmp |= ctrl_set;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	if (ctrl_reset != 0xFFFFFFFF) {
+		outl(tmp & ctrl_reset, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base,
+			   tmp & ctrl_reset);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+
+}
+
+/** @brief Last chunck of datas. We must reschedule sample counter.
+*	@note Last chunck.
+*	Leaving SC_RELOAD doesn't do any harm, but in some bad case can make extra interrupts.
+*	@warning When threshold is wrongly set some IRQ are lost.(!!!)
+*/
+void inline ai_reschedule_SC(me4600_ai_subdevice_t * instance)
+{
+	register uint32_t rest;
+
+	if (instance->data_required <= instance->ISM.global_read)
+		return;
+
+	rest = instance->data_required - instance->ISM.global_read;
+	if (rest < instance->fifo_irq_threshold) {	//End of work soon ....
+		PDEBUG("Rescheduling SC from %d to %d.\n",
+		       instance->fifo_irq_threshold, rest);
+		/// @note Write new value to SC <==  DANGER! This is not safe solution! We can miss some inputs.
+		outl(rest, instance->sample_counter_reg);
+		PDEBUG_REG("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->sample_counter_reg - instance->reg_base,
+			   rest);
+		instance->fifo_irq_threshold = rest;
+
+		if (rest < ME4600_AI_FIFO_MAX_SC) {
+			instance->ISM.next = rest;
+		} else {
+			instance->ISM.next = rest % ME4600_AI_FIFO_HALF;
+			if (instance->ISM.next + ME4600_AI_FIFO_HALF <
+			    ME4600_AI_FIFO_MAX_SC) {
+				instance->ISM.next += ME4600_AI_FIFO_HALF;
+			}
+		}
+	}
+}
+
+/** Start the ISM. All must be reseted before enter to this function. */
+void inline ai_data_acquisition_logic(me4600_ai_subdevice_t * instance)
+{
+	register uint32_t tmp;
+
+	if (!instance->data_required) {	//This is infinite aqusition.
+		if (!instance->fifo_irq_threshold) {	//No threshold provided. Set SC to 0.5*FIFO. Clear the SC's latch.
+			//Set the sample counter
+			outl(ME4600_AI_FIFO_HALF, instance->sample_counter_reg);
+			PDEBUG_REG
+			    ("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+			     instance->reg_base,
+			     instance->sample_counter_reg - instance->reg_base,
+			     ME4600_AI_FIFO_HALF);
+		} else {	//Threshold provided. Set SC to treshold. Clear the SC's latch.
+			//Set the sample counter
+			outl(instance->fifo_irq_threshold,
+			     instance->sample_counter_reg);
+			PDEBUG_REG
+			    ("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+			     instance->reg_base,
+			     instance->sample_counter_reg - instance->reg_base,
+			     instance->fifo_irq_threshold);
+		}
+
+		if (instance->fifo_irq_threshold < ME4600_AI_FIFO_MAX_SC) {	//Enable only sample counter's interrupt. Set reload bit. Clear the SC's latch.
+			spin_lock(instance->ctrl_reg_lock);
+			tmp = inl(instance->ctrl_reg);
+			tmp |= ME4600_AI_CTRL_BIT_SC_RELOAD;
+			tmp &= ~ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+			outl(tmp, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   tmp);
+			spin_unlock(instance->ctrl_reg_lock);
+			if (!instance->fifo_irq_threshold) {	//No threshold provided. Set ISM.next to 0.5*FIFO.
+				instance->ISM.next = ME4600_AI_FIFO_HALF;
+			} else {	//Threshold provided. Set ISM.next to treshold.
+				instance->ISM.next =
+				    instance->fifo_irq_threshold;
+			}
+		} else {	//Enable sample counter's and HF's interrupts.
+			spin_lock(instance->ctrl_reg_lock);
+			tmp = inl(instance->ctrl_reg);
+			tmp |= ME4600_AI_CTRL_BIT_SC_RELOAD;
+			tmp &=
+			    ~(ME4600_AI_CTRL_BIT_SC_IRQ_RESET |
+			      ME4600_AI_CTRL_BIT_HF_IRQ_RESET);
+			outl(tmp, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   tmp);
+			spin_unlock(instance->ctrl_reg_lock);
+
+			instance->ISM.next =
+			    instance->fifo_irq_threshold % ME4600_AI_FIFO_HALF;
+			if (instance->ISM.next + ME4600_AI_FIFO_HALF <
+			    ME4600_AI_FIFO_MAX_SC) {
+				instance->ISM.next += ME4600_AI_FIFO_HALF;
+			}
+		}
+	} else {		//This aqusition is limited to set number of data.
+		if (instance->fifo_irq_threshold >= instance->data_required) {	//Stupid situation.
+			instance->fifo_irq_threshold = 0;
+			PDEBUG
+			    ("Stupid situation: data_required(%d) < threshold(%d).\n",
+			     instance->fifo_irq_threshold,
+			     instance->data_required);
+		}
+
+		if (!instance->fifo_irq_threshold) {	//No threshold provided. Easy case: HF=read and SC=end.
+			//Set the sample counter to data_required.
+			outl(instance->data_required,
+			     instance->sample_counter_reg);
+			PDEBUG_REG
+			    ("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+			     instance->reg_base,
+			     instance->sample_counter_reg - instance->reg_base,
+			     instance->data_required);
+
+			//Reset the latches of sample counter and HF (if SC>FIFO).
+			//No SC reload!
+			spin_lock(instance->ctrl_reg_lock);
+			tmp = inl(instance->ctrl_reg);
+			tmp &=
+			    ~(ME4600_AI_CTRL_BIT_SC_IRQ_RESET |
+			      ME4600_AI_CTRL_BIT_SC_RELOAD);
+			if (instance->data_required >
+			    (ME4600_AI_FIFO_COUNT - 1)) {
+				tmp &= ~ME4600_AI_CTRL_BIT_HF_IRQ_RESET;
+				instance->ISM.next =
+				    instance->data_required %
+				    ME4600_AI_FIFO_HALF;
+				instance->ISM.next += ME4600_AI_FIFO_HALF;
+
+			} else {
+				instance->ISM.next = instance->data_required;
+			}
+			outl(tmp, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   tmp);
+			spin_unlock(instance->ctrl_reg_lock);
+
+		} else {	//The most general case. We have concret numbe of required data and threshold. SC=TH
+			//Set the sample counter to threshold.
+			outl(instance->fifo_irq_threshold,
+			     instance->sample_counter_reg);
+			PDEBUG_REG
+			    ("sample_counter_reg outl(0x%lX+0x%lX)=0x%x\n",
+			     instance->reg_base,
+			     instance->sample_counter_reg - instance->reg_base,
+			     instance->fifo_irq_threshold);
+
+			spin_lock(instance->ctrl_reg_lock);
+			tmp = inl(instance->ctrl_reg);
+			//In this moment we are sure that SC will come more than once.
+			tmp |= ME4600_AI_CTRL_BIT_SC_RELOAD;
+
+			if (instance->fifo_irq_threshold < ME4600_AI_FIFO_MAX_SC) {	//The threshold is so small that we do need HF.
+				tmp &= ~ME4600_AI_CTRL_BIT_SC_IRQ_RESET;
+				instance->ISM.next =
+				    instance->fifo_irq_threshold;
+			} else {	//The threshold is large. The HF must be use.
+				tmp &=
+				    ~(ME4600_AI_CTRL_BIT_SC_IRQ_RESET |
+				      ME4600_AI_CTRL_BIT_HF_IRQ_RESET);
+				instance->ISM.next =
+				    instance->fifo_irq_threshold %
+				    ME4600_AI_FIFO_HALF;
+				if (instance->ISM.next + ME4600_AI_FIFO_HALF <
+				    ME4600_AI_FIFO_MAX_SC) {
+					instance->ISM.next +=
+					    ME4600_AI_FIFO_HALF;
+				}
+			}
+			outl(tmp, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   tmp);
+			spin_unlock(instance->ctrl_reg_lock);
+		}
+	}
+}
+
+static int ai_mux_toggler(me4600_ai_subdevice_t * instance)
+{
+	uint32_t tmp;
+
+	PDEBUG("executed. idx=0\n");
+
+	outl(0, instance->scan_pre_timer_low_reg);
+	PDEBUG_REG("scan_pre_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_pre_timer_high_reg);
+	PDEBUG_REG("scan_pre_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_pre_timer_high_reg - instance->reg_base, 0);
+	outl(0, instance->scan_timer_low_reg);
+	PDEBUG_REG("scan_timer_low_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_low_reg - instance->reg_base, 0);
+	outl(0, instance->scan_timer_high_reg);
+	PDEBUG_REG("scan_timer_high_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->scan_timer_high_reg - instance->reg_base, 0);
+	outl(65, instance->chan_timer_reg);
+	PDEBUG_REG("chan_timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->chan_timer_reg - instance->reg_base, 65);
+	outl(65, instance->chan_pre_timer_reg);
+	PDEBUG_REG("chan_pre_timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->chan_pre_timer_reg - instance->reg_base, 65);
+
+	// Turn on internal reference.
+	tmp = inl(instance->ctrl_reg);
+	tmp |= ME4600_AI_CTRL_BIT_FULLSCALE;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	// Clear data and channel fifo.
+	tmp &=
+	    ~(ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	tmp |= ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	// Write channel entry.
+	outl(ME4600_AI_LIST_INPUT_DIFFERENTIAL |
+	     ME4600_AI_LIST_RANGE_UNIPOLAR_2_5 | 31,
+	     instance->channel_list_reg);
+	PDEBUG_REG("channel_list_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->channel_list_reg - instance->reg_base,
+		   ME4600_AI_LIST_INPUT_DIFFERENTIAL |
+		   ME4600_AI_LIST_RANGE_UNIPOLAR_2_5 | 31);
+
+	// Start conversion.
+	inl(instance->start_reg);
+	PDEBUG_REG("start_reg inl(0x%lX+0x%lX)\n", instance->reg_base,
+		   instance->start_reg - instance->reg_base);
+	udelay(10);
+
+	// Clear data and channel fifo.
+	tmp &=
+	    ~(ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	tmp |= ME4600_AI_CTRL_BIT_CHANNEL_FIFO | ME4600_AI_CTRL_BIT_DATA_FIFO;
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	// Write channel entry.
+	// ME4600_AI_LIST_INPUT_SINGLE_ENDED | ME4600_AI_LIST_RANGE_BIPOLAR_10 <= 0x0000
+	outl(ME4600_AI_LIST_INPUT_SINGLE_ENDED |
+	     ME4600_AI_LIST_RANGE_BIPOLAR_10, instance->channel_list_reg);
+	PDEBUG_REG("channel_list_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->channel_list_reg - instance->reg_base,
+		   ME4600_AI_LIST_INPUT_SINGLE_ENDED |
+		   ME4600_AI_LIST_RANGE_BIPOLAR_10);
+
+	// Start conversion.
+	inl(instance->start_reg);
+	PDEBUG_REG("start_reg inl(0x%lX+0x%lX)\n", instance->reg_base,
+		   instance->start_reg - instance->reg_base);
+	udelay(10);
+
+	// Clear control register.
+	tmp &= (ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+
+	return ME_ERRNO_SUCCESS;
+}
+
+/** @brief Copy rest of data from fifo to circular buffer.
+* @note Helper for STOP command. After FSM is stopped.
+* @note This is slow function that copy all remainig data from FIFO to buffer.
+*
+* @param instance The subdevice instance (pointer).
+*
+* @return On success: Number of copied values.
+* @return On error: Negative error code -ME_ERRNO_RING_BUFFER_OVERFLOW.
+*/
+static int inline ai_read_data_pooling(me4600_ai_subdevice_t * instance)
+{				/// @note This is time critical function!
+	int empty_space;
+	int copied = 0;
+	int status = ME_ERRNO_SUCCESS;
+
+	PDEBUG("Space left in circular buffer = %d.\n",
+	       me_circ_buf_space(&instance->circ_buf));
+
+	while ((empty_space = me_circ_buf_space(&instance->circ_buf))) {
+		if (!(status = inl(instance->status_reg) & ME4600_AI_STATUS_BIT_EF_DATA)) {	//No more data. status = ME_ERRNO_SUCCESS = 0
+			break;
+		}
+		*(instance->circ_buf.buf + instance->circ_buf.head) =
+		    (inw(instance->data_reg) ^ 0x8000);
+		instance->circ_buf.head++;
+		instance->circ_buf.head &= instance->circ_buf.mask;
+	}
+
+#ifdef MEDEBUG_ERROR
+	if (!status)
+		PDEBUG
+		    ("Copied all remaining datas (%d) from FIFO to circular buffer.\n",
+		     copied);
+	else {
+		PDEBUG("No more empty space in buffer.\n");
+		PDEBUG("Copied %d datas from FIFO to circular buffer.\n",
+		       copied);
+		PDEBUG("FIFO still not empty.\n");
+	}
+#endif
+	return (!status) ? copied : -ME_ERRNO_RING_BUFFER_OVERFLOW;
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void me4600_ai_work_control_task(void *subdevice)
+#else
+static void me4600_ai_work_control_task(struct work_struct *work)
+#endif
+{
+	me4600_ai_subdevice_t *instance;
+	uint32_t status;
+	uint32_t ctrl;
+	unsigned long cpu_flags = 0;
+	int reschedule = 0;
+	int signaling = 0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	instance = (me4600_ai_subdevice_t *) subdevice;
+#else
+	instance =
+	    container_of((void *)work, me4600_ai_subdevice_t, ai_control_task);
+#endif
+	PINFO("<%s: %ld> executed.\n", __FUNCTION__, jiffies);
+
+	status = inl(instance->status_reg);
+	PDEBUG_REG("status_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->status_reg - instance->reg_base, status);
+
+	switch (instance->status) {	// Checking actual mode.
+		// Not configured for work.
+	case ai_status_none:
+		break;
+
+		//This are stable modes. No need to do anything. (?)
+	case ai_status_single_configured:
+	case ai_status_stream_configured:
+	case ai_status_stream_fifo_error:
+	case ai_status_stream_buffer_error:
+	case ai_status_stream_error:
+		PERROR("Shouldn't be running!.\n");
+		break;
+
+		// Stream modes
+	case ai_status_stream_run_wait:
+		if (status & ME4600_AI_STATUS_BIT_FSM) {	// ISM started..
+			instance->status = ai_status_stream_run;
+			// Signal the end of wait for start.
+			signaling = 1;
+			// Wait now for stop.
+			reschedule = 1;
+			break;
+
+			// Check timeout.
+			if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+				PDEBUG("Timeout reached.\n");
+				// Stop all actions. No conditions! Block interrupts. Reset FIFO => Too late!
+				ai_stop_isr(instance);
+
+				instance->status = ai_status_stream_end;
+
+				// Signal the end.
+				signaling = 1;
+			}
+		}
+		break;
+
+	case ai_status_stream_run:
+		// Wait for stop ISM.
+		reschedule = 1;
+		break;
+
+	case ai_status_stream_end_wait:
+		if (!(status & ME4600_AI_STATUS_BIT_FSM)) {	// ISM stoped. Overwrite ISR.
+			instance->status = ai_status_stream_end;
+			// Signal the end of wait for stop.
+			signaling = 1;
+		} else {
+			// Wait for stop ISM.
+			reschedule = 1;
+		}
+		break;
+
+	case ai_status_stream_end:
+		//End work.
+		if (status & ME4600_AI_STATUS_BIT_FSM) {	// Still working? Stop it!
+			PERROR
+			    ("Status is 'ai_status_stream_end' but hardware is still working!\n");
+			spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    (ME4600_AI_CTRL_BIT_IMMEDIATE_STOP |
+			     ME4600_AI_CTRL_BIT_HF_IRQ_RESET |
+			     ME4600_AI_CTRL_BIT_SC_IRQ_RESET);
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(instance->ctrl_reg_lock,
+					       cpu_flags);
+		}
+		break;
+
+	default:
+		PERROR_CRITICAL("Status is in wrong state (%d)!\n",
+				instance->status);
+		instance->status = ai_status_stream_error;
+		// Signal the end.
+		signaling = 1;
+		break;
+
+	}
+
+	if (signaling) {	//Signal it.
+		wake_up_interruptible_all(&instance->wait_queue);
+	}
+
+	if (instance->ai_control_task_flag && reschedule) {	// Reschedule task
+		queue_delayed_work(instance->me4600_workqueue,
+				   &instance->ai_control_task, 1);
+	} else {
+		PINFO("<%s> Ending control task.\n", __FUNCTION__);
+	}
+
+}

+ 180 - 0
drivers/staging/meilhaus/me4600_ai.h

@@ -0,0 +1,180 @@
+/**
+ * @file me4600_ai.h
+ *
+ * @brief Meilhaus ME-4000 analog input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke  (k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_AI_H_
+#define _ME4600_AI_H_
+
+#include <linux/version.h>
+#include "mesubdevice.h"
+#include "meioctl.h"
+#include "mecirc_buf.h"
+
+#ifdef __KERNEL__
+
+#define ME4600_AI_MAX_DATA				0xFFFF
+
+#ifdef ME_SYNAPSE
+# define ME4600_AI_CIRC_BUF_SIZE_ORDER	8	// 2^n PAGES =>> Maximum value of 1MB for Synapse
+#else
+# define ME4600_AI_CIRC_BUF_SIZE_ORDER	5	// 2^n PAGES =>> 128KB
+#endif
+#define ME4600_AI_CIRC_BUF_SIZE 		PAGE_SIZE<<ME4600_AI_CIRC_BUF_SIZE_ORDER	// Buffer size in bytes.
+
+#ifdef _CBUFF_32b_t
+# define ME4600_AI_CIRC_BUF_COUNT		((ME4600_AI_CIRC_BUF_SIZE) / sizeof(uint32_t))	// Size in values
+#else
+# define ME4600_AI_CIRC_BUF_COUNT		((ME4600_AI_CIRC_BUF_SIZE) / sizeof(uint16_t))	// Size in values
+#endif
+
+#define ME4600_AI_FIFO_HALF				1024	//ME4600_AI_FIFO_COUNT/2                //1024
+#define ME4600_AI_FIFO_MAX_SC			1352	//0.66*ME4600_AI_FIFO_COUNT             //1352
+
+typedef enum ME4600_AI_STATUS {
+	ai_status_none = 0,
+	ai_status_single_configured,
+	ai_status_stream_configured,
+	ai_status_stream_run_wait,
+	ai_status_stream_run,
+	ai_status_stream_end_wait,
+	ai_status_stream_end,
+	ai_status_stream_fifo_error,
+	ai_status_stream_buffer_error,
+	ai_status_stream_error,
+	ai_status_last
+} ME4600_AI_STATUS;
+
+typedef struct me4600_single_config_entry {
+	unsigned short status;
+	uint32_t entry;
+	uint32_t ctrl;
+} me4600_single_config_entry_t;
+
+typedef struct me4600_range_entry {
+	int min;
+	int max;
+} me4600_range_entry_t;
+
+typedef struct me4600_ai_ISM {
+	volatile unsigned int global_read;				/**< The number of data read in total. */
+	volatile unsigned int read;						/**< The number of data read for this chunck. */
+	volatile unsigned int next;						/**< The number of data request by user. */
+} me4600_ai_ISM_t;
+
+typedef struct me4600_ai_timeout {
+	unsigned long start_time;
+	unsigned long delay;
+} me4600_ai_timeout_t;
+
+/**
+ * @brief The ME-4000 analog input subdevice class.
+ */
+typedef struct me4600_ai_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;							/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;						/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;						/**< Spin lock to protect #ctrl_reg from concurrent access. */
+
+	/* Hardware feautres */
+	unsigned int irq;								/**< The interrupt request number assigned by the PCI BIOS. */
+	int isolated;									/**< Marks if this subdevice is on an optoisolated device. */
+	int sh;											/**< Marks if this subdevice has sample and hold devices. */
+
+	unsigned int channels;							/**< The number of channels available on this subdevice. */
+	me4600_single_config_entry_t single_config[32];	/**< The configuration set for single acquisition. */
+
+	unsigned int data_required;						/**< The number of data request by user. */
+	unsigned int fifo_irq_threshold;				/**< The user adjusted FIFO high water interrupt level. */
+	unsigned int chan_list_len;						/**< The length of the user defined channel list. */
+
+	me4600_ai_ISM_t ISM;							/**< The information request by Interrupt-State-Machine. */
+	volatile enum ME4600_AI_STATUS status;			/**< The current stream status flag. */
+	me4600_ai_timeout_t timeout;					/**< The timeout for start in blocking and non-blocking mode. */
+
+											/* Registers *//**< All registers are 32 bits long. */
+	unsigned long ctrl_reg;
+	unsigned long status_reg;
+	unsigned long channel_list_reg;
+	unsigned long data_reg;
+	unsigned long chan_timer_reg;
+	unsigned long chan_pre_timer_reg;
+	unsigned long scan_timer_low_reg;
+	unsigned long scan_timer_high_reg;
+	unsigned long scan_pre_timer_low_reg;
+	unsigned long scan_pre_timer_high_reg;
+	unsigned long start_reg;
+	unsigned long irq_status_reg;
+	unsigned long sample_counter_reg;
+
+	unsigned int ranges_len;
+	me4600_range_entry_t ranges[4];					/**< The ranges available on this subdevice. */
+
+	/* Software buffer */
+	me_circ_buf_t circ_buf;							/**< Circular buffer holding measurment data. */
+	wait_queue_head_t wait_queue;					/**< Wait queue to put on tasks waiting for data to arrive. */
+
+	struct workqueue_struct *me4600_workqueue;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct ai_control_task;
+#else
+	struct delayed_work ai_control_task;
+#endif
+
+	volatile int ai_control_task_flag;				/**< Flag controling reexecuting of control task */
+
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me4600_ai_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-4000 analog input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param channels The number of analog input channels available on this subdevice.
+ * @param channels The number of analog input ranges available on this subdevice.
+ * @param isolated Flag indicating if this device is opto isolated.
+ * @param sh Flag indicating if sample and hold devices are available.
+ * @param irq The irq number assigned by PCI BIOS.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the control register from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me4600_ai_subdevice_t *me4600_ai_constructor(uint32_t reg_base,
+					     unsigned int channels,
+					     unsigned int ranges,
+					     int isolated,
+					     int sh,
+					     int irq,
+					     spinlock_t * ctrl_reg_lock,
+					     struct workqueue_struct
+					     *me4600_wq);
+
+#endif
+#endif

+ 107 - 0
drivers/staging/meilhaus/me4600_ai_reg.h

@@ -0,0 +1,107 @@
+/**
+ * @file me4600_ai_reg.h
+ *
+ * @brief ME-4000 analog input subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_AI_REG_H_
+#define _ME4600_AI_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME4600_AI_CTRL_REG					0x74	// _/W
+#define ME4600_AI_STATUS_REG				0x74	// R/_
+#define ME4600_AI_CHANNEL_LIST_REG			0x78	// _/W
+#define ME4600_AI_DATA_REG					0x7C	// R/_
+#define ME4600_AI_CHAN_TIMER_REG			0x80	// _/W
+#define ME4600_AI_CHAN_PRE_TIMER_REG		0x84	// _/W
+#define ME4600_AI_SCAN_TIMER_LOW_REG		0x88	// _/W
+#define ME4600_AI_SCAN_TIMER_HIGH_REG		0x8C	// _/W
+#define ME4600_AI_SCAN_PRE_TIMER_LOW_REG	0x90	// _/W
+#define ME4600_AI_SCAN_PRE_TIMER_HIGH_REG	0x94	// _/W
+#define ME4600_AI_START_REG					0x98	// R/_
+
+#define ME4600_AI_SAMPLE_COUNTER_REG		0xC0	// _/W
+
+#define ME4600_AI_CTRL_BIT_MODE_0			0x00000001
+#define ME4600_AI_CTRL_BIT_MODE_1			0x00000002
+#define ME4600_AI_CTRL_BIT_MODE_2			0x00000004
+#define ME4600_AI_CTRL_BIT_SAMPLE_HOLD		0x00000008
+#define ME4600_AI_CTRL_BIT_IMMEDIATE_STOP	0x00000010
+#define ME4600_AI_CTRL_BIT_STOP				0x00000020
+#define ME4600_AI_CTRL_BIT_CHANNEL_FIFO		0x00000040
+#define ME4600_AI_CTRL_BIT_DATA_FIFO		0x00000080
+#define ME4600_AI_CTRL_BIT_FULLSCALE		0x00000100
+#define ME4600_AI_CTRL_BIT_OFFSET			0x00000200
+#define ME4600_AI_CTRL_BIT_EX_TRIG_ANALOG	0x00000400
+#define ME4600_AI_CTRL_BIT_EX_TRIG			0x00000800
+#define ME4600_AI_CTRL_BIT_EX_TRIG_FALLING	0x00001000
+#define ME4600_AI_CTRL_BIT_EX_IRQ			0x00002000
+#define ME4600_AI_CTRL_BIT_EX_IRQ_RESET		0x00004000
+#define ME4600_AI_CTRL_BIT_LE_IRQ			0x00008000
+#define ME4600_AI_CTRL_BIT_LE_IRQ_RESET		0x00010000
+#define ME4600_AI_CTRL_BIT_HF_IRQ			0x00020000
+#define ME4600_AI_CTRL_BIT_HF_IRQ_RESET		0x00040000
+#define ME4600_AI_CTRL_BIT_SC_IRQ			0x00080000
+#define ME4600_AI_CTRL_BIT_SC_IRQ_RESET		0x00100000
+#define ME4600_AI_CTRL_BIT_SC_RELOAD		0x00200000
+#define ME4600_AI_CTRL_BIT_EX_TRIG_BOTH		0x80000000
+
+#define ME4600_AI_STATUS_BIT_EF_CHANNEL		0x00400000
+#define ME4600_AI_STATUS_BIT_HF_CHANNEL		0x00800000
+#define ME4600_AI_STATUS_BIT_FF_CHANNEL		0x01000000
+#define ME4600_AI_STATUS_BIT_EF_DATA		0x02000000
+#define ME4600_AI_STATUS_BIT_HF_DATA		0x04000000
+#define ME4600_AI_STATUS_BIT_FF_DATA		0x08000000
+#define ME4600_AI_STATUS_BIT_LE				0x10000000
+#define ME4600_AI_STATUS_BIT_FSM			0x20000000
+
+#define ME4600_AI_CTRL_RPCI_FIFO			0x40000000	//Always set to zero!
+
+#define ME4600_AI_BASE_FREQUENCY			33E6
+
+#define ME4600_AI_MIN_ACQ_TICKS				66LL
+#define ME4600_AI_MAX_ACQ_TICKS				0xFFFFFFFFLL
+
+#define ME4600_AI_MIN_SCAN_TICKS			66LL
+#define ME4600_AI_MAX_SCAN_TICKS			0xFFFFFFFFFLL
+
+#define ME4600_AI_MIN_CHAN_TICKS			66LL
+#define ME4600_AI_MAX_CHAN_TICKS			0xFFFFFFFFLL
+
+#define ME4600_AI_FIFO_COUNT				2048
+
+#define ME4600_AI_LIST_COUNT				1024
+
+#define ME4600_AI_LIST_INPUT_SINGLE_ENDED	0x000
+#define ME4600_AI_LIST_INPUT_DIFFERENTIAL	0x020
+
+#define ME4600_AI_LIST_RANGE_BIPOLAR_10		0x000
+#define ME4600_AI_LIST_RANGE_BIPOLAR_2_5	0x040
+#define ME4600_AI_LIST_RANGE_UNIPOLAR_10	0x080
+#define ME4600_AI_LIST_RANGE_UNIPOLAR_2_5	0x0C0
+
+#define ME4600_AI_LIST_LAST_ENTRY		0x100
+
+#endif
+#endif

+ 6011 - 0
drivers/staging/meilhaus/me4600_ao.c

@@ -0,0 +1,6011 @@
+/**
+ * @file me4600_ao.c
+ *
+ * @brief ME-4000 analog output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+///Common part. (For normal and Bosch builds.)
+
+/* Includes
+ */
+
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "meids.h"
+#include "me4600_reg.h"
+#include "me4600_ao_reg.h"
+#include "me4600_ao.h"
+
+/* Defines
+ */
+
+static int me4600_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range);
+
+static int me4600_ao_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count);
+
+static int me4600_ao_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata);
+
+static int me4600_ao_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks);
+
+static int me4600_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number);
+
+static int me4600_ao_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype);
+
+static int me4600_ao_query_subdevice_caps(me_subdevice_t * subdevice,
+					  int *caps);
+
+static int me4600_ao_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count);
+
+#ifndef BOSCH
+/// @note NORMAL BUILD
+/// @author Krzysztof Gantzke   (k.gantzke@meilhaus.de)
+/* Includes
+ */
+
+# include <linux/workqueue.h>
+
+/* Defines
+ */
+
+/** Remove subdevice.
+*/
+static void me4600_ao_destructor(struct me_subdevice *subdevice);
+
+/** Reset subdevice. Stop all actions. Reset registry. Disable FIFO. Set output to 0V and status to 'none'.
+*/
+static int me4600_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags);
+
+/** Set output as single
+*/
+static int me4600_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags);
+
+/** Pass to user actual value of output.
+*/
+static int me4600_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags);
+
+/** Write to output requed value.
+*/
+static int me4600_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags);
+
+/** Set output as streamed device.
+*/
+static int me4600_ao_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags);
+
+/** Wait for / Check empty space in buffer.
+*/
+static int me4600_ao_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags);
+
+/** Start streaming.
+*/
+static int me4600_ao_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags);
+
+/** Check actual state. / Wait for end.
+*/
+static int me4600_ao_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags);
+
+/** Stop streaming.
+*/
+static int me4600_ao_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags);
+
+/** Write datas to buffor.
+*/
+static int me4600_ao_io_stream_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int write_mode,
+				     int *values, int *count, int flags);
+
+/** Interrupt handler. Copy from buffer to FIFO.
+*/
+static irqreturn_t me4600_ao_isr(int irq, void *dev_id
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+				 , struct pt_regs *regs
+#endif
+    );
+/** Copy data from circular buffer to fifo (fast) in wraparound mode.
+*/
+int inline ao_write_data_wraparound(me4600_ao_subdevice_t * instance, int count,
+				    int start_pos);
+
+/** Copy data from circular buffer to fifo (fast).
+*/
+int inline ao_write_data(me4600_ao_subdevice_t * instance, int count,
+			 int start_pos);
+
+/** Copy data from circular buffer to fifo (slow).
+*/
+int inline ao_write_data_pooling(me4600_ao_subdevice_t * instance, int count,
+				 int start_pos);
+
+/** Copy data from user space to circular buffer.
+*/
+int inline ao_get_data_from_user(me4600_ao_subdevice_t * instance, int count,
+				 int *user_values);
+
+/** Stop presentation. Preserve FIFOs.
+*/
+int inline ao_stop_immediately(me4600_ao_subdevice_t * instance);
+
+/** Task for asynchronical state verifying.
+*/
+static void me4600_ao_work_control_task(
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+					       void *subdevice
+#else
+					       struct work_struct *work
+#endif
+    );
+/* Functions
+ */
+
+static int me4600_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t tmp;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	instance->status = ao_status_none;
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+	instance->timeout.delay = 0;
+	instance->timeout.start_time = jiffies;
+
+	//Stop state machine.
+	err = ao_stop_immediately(instance);
+
+	//Remove from synchronous start.
+	spin_lock(instance->preload_reg_lock);
+	tmp = inl(instance->preload_reg);
+	tmp &=
+	    ~((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+	outl(tmp, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, tmp);
+	*instance->preload_flags &=
+	    ~((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+	spin_unlock(instance->preload_reg_lock);
+
+	//Set single mode, dissable FIFO, dissable external trigger, set output to analog, block interrupt.
+	outl(ME4600_AO_MODE_SINGLE | ME4600_AO_CTRL_BIT_STOP |
+	     ME4600_AO_CTRL_BIT_IMMEDIATE_STOP | ME4600_AO_CTRL_BIT_RESET_IRQ,
+	     instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base,
+		   ME4600_AO_MODE_SINGLE | ME4600_AO_CTRL_BIT_STOP |
+		   ME4600_AO_CTRL_BIT_IMMEDIATE_STOP |
+		   ME4600_AO_CTRL_BIT_RESET_IRQ);
+
+	//Set output to 0V
+	outl(0x8000, instance->single_reg);
+	PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->single_reg - instance->reg_base, 0x8000);
+
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+	instance->preloaded_count = 0;
+	instance->data_count = 0;
+	instance->single_value = 0x8000;
+	instance->single_value_in_fifo = 0x8000;
+
+	//Set status to signal that device is unconfigured.
+	instance->status = ao_status_none;
+
+	//Signal reset if user is on wait.
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t ctrl;
+	uint32_t sync;
+	unsigned long cpu_flags;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	// Checking parameters
+	if (flags) {
+		PERROR
+		    ("Invalid flag specified. Must be ME_IO_SINGLE_CONFIG_NO_FLAGS.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	switch (trig_type) {
+	case ME_TRIG_TYPE_SW:
+		if (trig_edge != ME_TRIG_EDGE_NONE) {
+			PERROR
+			    ("Invalid trigger edge. Software trigger has not edge.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		switch (trig_edge) {
+		case ME_TRIG_EDGE_ANY:
+		case ME_TRIG_EDGE_RISING:
+		case ME_TRIG_EDGE_FALLING:
+			break;
+
+		default:
+			PERROR("Invalid trigger edge.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+		break;
+
+	default:
+		PERROR
+		    ("Invalid trigger type. Trigger must be software or digital.\n");
+		return ME_ERRNO_INVALID_TRIG_TYPE;
+	}
+
+	if ((trig_chan != ME_TRIG_CHAN_DEFAULT)
+	    && (trig_chan != ME_TRIG_CHAN_SYNCHRONOUS)) {
+		PERROR("Invalid trigger channel specified.\n");
+		return ME_ERRNO_INVALID_TRIG_CHAN;
+	}
+
+	if (ref != ME_REF_AO_GROUND) {
+		PERROR
+		    ("Invalid reference. Analog outputs have to have got REF_AO_GROUND.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if (single_config != 0) {
+		PERROR
+		    ("Invalid single config specified. Only one range for anlog outputs is available.\n");
+		return ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+
+	if (channel != 0) {
+		PERROR
+		    ("Invalid channel number specified. Analog output have only one channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Subdevice running in stream mode!
+	if ((instance->status >= ao_status_stream_run_wait)
+	    && (instance->status < ao_status_stream_end)) {
+		PERROR("Subdevice is busy.\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+/// @note For single all calls (config and write) are erasing previous state!
+
+	instance->status = ao_status_none;
+
+	// Correct single mirrors
+	instance->single_value_in_fifo = instance->single_value;
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+	// Set control register.
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	// Set stop bit. Stop streaming mode.
+	ctrl = inl(instance->ctrl_reg);
+	//Reset all bits.
+	ctrl = ME4600_AO_CTRL_BIT_IMMEDIATE_STOP | ME4600_AO_CTRL_BIT_STOP;
+
+	if (trig_type == ME_TRIG_TYPE_EXT_DIGITAL) {
+		PINFO("External digital trigger.\n");
+
+		if (trig_edge == ME_TRIG_EDGE_ANY) {
+//                              ctrl |= ME4600_AO_CTRL_BIT_EX_TRIG_EDGE | ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			instance->ctrl_trg =
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+		} else if (trig_edge == ME_TRIG_EDGE_FALLING) {
+//                              ctrl |= ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+			instance->ctrl_trg = ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+		} else if (trig_edge == ME_TRIG_EDGE_RISING) {
+			instance->ctrl_trg = 0x0;
+		}
+	} else if (trig_type == ME_TRIG_TYPE_SW) {
+		PDEBUG("Software trigger\n");
+		instance->ctrl_trg = 0x0;
+	}
+
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	// Set preload/synchronization register.
+	spin_lock(instance->preload_reg_lock);
+	if (trig_type == ME_TRIG_TYPE_SW) {
+		*instance->preload_flags &=
+		    ~(ME4600_AO_SYNC_EXT_TRIG << instance->ao_idx);
+	} else			//if (trig_type == ME_TRIG_TYPE_EXT_DIGITAL)
+	{
+		*instance->preload_flags |=
+		    ME4600_AO_SYNC_EXT_TRIG << instance->ao_idx;
+	}
+
+	if (trig_chan == ME_TRIG_CHAN_DEFAULT) {
+		*instance->preload_flags &=
+		    ~(ME4600_AO_SYNC_HOLD << instance->ao_idx);
+	} else			//if (trig_chan == ME_TRIG_CHAN_SYNCHRONOUS)
+	{
+		*instance->preload_flags |=
+		    ME4600_AO_SYNC_HOLD << instance->ao_idx;
+	}
+
+	//Reset hardware register
+	sync = inl(instance->preload_reg);
+	PDEBUG_REG("preload_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync);
+	sync &= ~(ME4600_AO_SYNC_EXT_TRIG << instance->ao_idx);
+	sync |= ME4600_AO_SYNC_HOLD << instance->ao_idx;
+
+	//Output configured in default (safe) mode.
+	outl(sync, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync);
+	spin_unlock(instance->preload_reg_lock);
+
+	instance->status = ao_status_single_configured;
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	unsigned long j;
+	unsigned long delay = 0;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags & ~ME_IO_SINGLE_NONBLOCKING) {
+		PERROR("Invalid flag specified. %d\n", flags);
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if ((instance->status >= ao_status_stream_configured)
+	    && (instance->status <= ao_status_stream_end)) {
+		PERROR("Subdevice not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	ME_SUBDEVICE_ENTER;
+	if ((!flags) && (instance->status == ao_status_single_run_wait)) {	//Blocking mode. Wait for trigger.
+		if (time_out) {
+			delay = (time_out * HZ) / 1000;
+			if (delay == 0)
+				delay = 1;
+		}
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Events are signaled when status change. This procedure has own timeout.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_single_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if (instance->status == ao_status_none) {
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+
+			PDEBUG("Timeout reached.\n");
+			err = ME_ERRNO_TIMEOUT;
+		}
+
+		*value =
+		    (!err) ? instance->single_value_in_fifo : instance->
+		    single_value;
+	} else {		//Non-blocking mode
+		//Read value
+		*value = instance->single_value;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags;
+	unsigned long j;
+	unsigned long delay = 0x0;
+
+	//Registry handling variables.
+	uint32_t sync_mask;
+	uint32_t mode;
+	uint32_t tmp;
+	uint32_t ctrl;
+	uint32_t status;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags &
+	    ~(ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS |
+	      ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (value & ~ME4600_AO_MAX_DATA) {
+		PERROR("Invalid value provided.\n");
+		return ME_ERRNO_VALUE_OUT_OF_RANGE;
+	}
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if ((instance->status == ao_status_none)
+	    || (instance->status > ao_status_single_end)) {
+		PERROR("Subdevice not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+/// @note For single all calls (config and write) are erasing previous state!
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	// Correct single mirrors
+	instance->single_value_in_fifo = instance->single_value;
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	instance->single_value_in_fifo = value;
+
+	ctrl = inl(instance->ctrl_reg);
+
+	if (!instance->fifo) {	//No FIFO
+		//Set the single mode.
+		ctrl &= ~ME4600_AO_CTRL_MODE_MASK;
+
+		//Write value
+		PDEBUG("Write value\n");
+		outl(value, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, value);
+	} else {		// mix-mode
+		//Set speed
+		outl(ME4600_AO_MIN_CHAN_TICKS - 1, instance->timer_reg);
+		PDEBUG_REG("timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->timer_reg - instance->reg_base,
+			   (int)ME4600_AO_MIN_CHAN_TICKS);
+		instance->hardware_stop_delay = HZ / 10;	//100ms
+
+		status = inl(instance->status_reg);
+
+		//Set the continous mode.
+		ctrl &= ~ME4600_AO_CTRL_MODE_MASK;
+		ctrl |= ME4600_AO_MODE_CONTINUOUS;
+
+		//Prepare FIFO
+		if (!(ctrl & ME4600_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO wasn't enabeled. Do it.
+			PINFO("Enableing FIFO.\n");
+			ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			ctrl |=
+			    ME4600_AO_CTRL_BIT_ENABLE_FIFO |
+			    ME4600_AO_CTRL_BIT_RESET_IRQ;
+		} else {	//Check if FIFO is empty
+			if (status & ME4600_AO_STATUS_BIT_EF) {	//FIFO not empty
+				PINFO("Reseting FIFO.\n");
+				ctrl &=
+				    ~(ME4600_AO_CTRL_BIT_ENABLE_FIFO |
+				      ME4600_AO_CTRL_BIT_ENABLE_IRQ);
+				ctrl |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+				outl(ctrl, instance->ctrl_reg);
+				PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->ctrl_reg -
+					   instance->reg_base, ctrl);
+
+				ctrl |=
+				    ME4600_AO_CTRL_BIT_ENABLE_FIFO |
+				    ME4600_AO_CTRL_BIT_RESET_IRQ;
+			} else {	//FIFO empty, only interrupt needs to be disabled!
+				ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+				ctrl |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+			}
+		}
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		//Write output - 1 value to FIFO
+		if (instance->ao_idx & 0x1) {
+			outl(value <<= 16, instance->fifo_reg);
+			PDEBUG_REG("fifo_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->fifo_reg - instance->reg_base,
+				   value <<= 16);
+		} else {
+			outl(value, instance->fifo_reg);
+			PDEBUG_REG("fifo_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->fifo_reg - instance->reg_base,
+				   value);
+		}
+	}
+
+	mode = *instance->preload_flags >> instance->ao_idx;
+	mode &= (ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG);
+
+	PINFO("Triggering mode: 0x%x\n", mode);
+
+	spin_lock(instance->preload_reg_lock);
+	sync_mask = inl(instance->preload_reg);
+	PDEBUG_REG("preload_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync_mask);
+	switch (mode) {
+	case 0:		//Individual software
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if (!instance->fifo) {	// No FIFO - In this case resetting 'ME4600_AO_SYNC_HOLD' will trigger output.
+			if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != ME4600_AO_SYNC_HOLD) {	//Now we can set correct mode. This is exception. It is set to synchronous and triggered later.
+				sync_mask &=
+				    ~(ME4600_AO_SYNC_EXT_TRIG << instance->
+				      ao_idx);
+				sync_mask |=
+				    ME4600_AO_SYNC_HOLD << instance->ao_idx;
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		} else {	// FIFO
+			if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != 0x0) {	//Now we can set correct mode.
+				sync_mask &=
+				    ~((ME4600_AO_SYNC_EXT_TRIG |
+				       ME4600_AO_SYNC_HOLD) << instance->
+				      ao_idx);
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		}
+		instance->single_value = value;
+		break;
+
+	case ME4600_AO_SYNC_EXT_TRIG:	//Individual hardware
+		ctrl |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if (!instance->fifo) {	// No FIFO - In this case resetting 'ME4600_AO_SYNC_HOLD' will trigger output.
+			if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != ME4600_AO_SYNC_HOLD) {	//Now we can set correct mode
+				sync_mask &=
+				    ~(ME4600_AO_SYNC_EXT_TRIG << instance->
+				      ao_idx);
+				sync_mask |=
+				    ME4600_AO_SYNC_HOLD << instance->ao_idx;
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		} else {	// FIFO
+			if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != 0x0) {	//Now we can set correct mode.
+				sync_mask &=
+				    ~((ME4600_AO_SYNC_EXT_TRIG |
+				       ME4600_AO_SYNC_HOLD) << instance->
+				      ao_idx);
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		}
+		break;
+
+	case ME4600_AO_SYNC_HOLD:	//Synchronous software
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+//                                      if((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != ME4600_AO_SYNC_HOLD)
+		if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != (ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG)) {	//Now we can set correct mode
+			sync_mask |=
+			    ME4600_AO_SYNC_EXT_TRIG << instance->ao_idx;
+//                                              sync_mask &= ~(ME4600_AO_SYNC_EXT_TRIG << instance->ao_idx);
+			sync_mask |= ME4600_AO_SYNC_HOLD << instance->ao_idx;
+
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		}
+		break;
+
+	case (ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG):	//Synchronous hardware
+		ctrl |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+		if ((sync_mask & ((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != (ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG)) {	//Now we can set correct mode
+			sync_mask |=
+			    (ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) <<
+			    instance->ao_idx;
+
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		}
+		break;
+	}
+//              spin_unlock(instance->preload_reg_lock);        // Moved down.
+
+	//Activate ISM (remove 'stop' bits)
+	ctrl &=
+	    ~(ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+	      ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH);
+	ctrl |= instance->ctrl_trg;
+	ctrl &= ~(ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+/// @note When flag 'ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS' is set than output is triggered. ALWAYS!
+
+	if (!instance->fifo) {	//No FIFO
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Fired all software synchronous outputs.
+			tmp = ~(*instance->preload_flags | 0xFFFF0000);
+			PINFO
+			    ("Fired all software synchronous outputs. mask:0x%08x\n",
+			     tmp);
+			tmp |= sync_mask & 0xFFFF0000;
+			// Add this channel to list
+			tmp &= ~(ME4600_AO_SYNC_HOLD << instance->ao_idx);
+
+			//Fire
+			PINFO("Software trigger.\n");
+			outl(tmp, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   tmp);
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		} else if (!mode) {	// Add this channel to list
+			outl(sync_mask &
+			     ~(ME4600_AO_SYNC_HOLD << instance->ao_idx),
+			     instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask & ~(ME4600_AO_SYNC_HOLD <<
+						 instance->ao_idx));
+
+			//Fire
+			PINFO("Software trigger.\n");
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		}
+
+	} else {		// mix-mode - begin
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Trigger outputs
+			//Add channel to start list
+			outl(sync_mask |
+			     (ME4600_AO_SYNC_HOLD << instance->ao_idx),
+			     instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask | (ME4600_AO_SYNC_HOLD <<
+						instance->ao_idx));
+
+			//Fire
+			PINFO
+			    ("Fired all software synchronous outputs by software trigger.\n");
+			outl(0x8000, instance->single_reg);
+			PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->single_reg - instance->reg_base,
+				   0x8000);
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		} else if (!mode) {	//Trigger outputs
+/*			//Remove channel from start list //<== Unnecessary. Removed.
+			outl(sync_mask & ~(ME4600_AO_SYNC_HOLD << instance->ao_idx), instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, tmp);
+*/
+			//Fire
+			PINFO("Software trigger.\n");
+			outl(0x8000, instance->single_reg);
+			PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->single_reg - instance->reg_base,
+				   0x8000);
+
+/*			//Restore save settings //<== Unnecessary. Removed.
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, sync_mask);
+*/
+		}
+	}
+	spin_unlock(instance->preload_reg_lock);
+
+	j = jiffies;
+	instance->status = ao_status_single_run_wait;
+
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = j;
+	instance->ao_control_task_flag = 1;
+	queue_delayed_work(instance->me4600_workqueue,
+			   &instance->ao_control_task, 1);
+
+	if (!(flags & ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_single_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if (((!delay) || ((jiffies - j) <= delay))
+		    && (instance->status != ao_status_single_end)) {
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->ao_control_task_flag = 0;
+			cancel_delayed_work(&instance->ao_control_task);
+			ao_stop_immediately(instance);
+			instance->status = ao_status_none;
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+			if (instance->status == ao_status_single_end) {
+				PDEBUG("Timeout reached.\n");
+			} else {
+				if ((jiffies - j) > delay) {
+					PERROR
+					    ("Timeout reached. Not handled by control task!\n");
+				} else {
+					PERROR
+					    ("Timeout reached. Signal come but status is strange: %d\n",
+					     instance->status);
+				}
+
+				ao_stop_immediately(instance);
+			}
+
+			instance->ao_control_task_flag = 0;
+			cancel_delayed_work(&instance->ao_control_task);
+			instance->status = ao_status_single_end;
+			err = ME_ERRNO_TIMEOUT;
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t ctrl;
+	unsigned long cpu_flags;
+	uint64_t conv_ticks;
+	unsigned int conv_start_ticks_low = trigger->iConvStartTicksLow;
+	unsigned int conv_start_ticks_high = trigger->iConvStartTicksHigh;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	conv_ticks =
+	    (uint64_t) conv_start_ticks_low +
+	    ((uint64_t) conv_start_ticks_high << 32);
+
+	if (flags &
+	    ~(ME_IO_STREAM_CONFIG_HARDWARE_ONLY | ME_IO_STREAM_CONFIG_WRAPAROUND
+	      | ME_IO_STREAM_CONFIG_BIT_PATTERN)) {
+		PERROR("Invalid flags.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (flags & ME_IO_STREAM_CONFIG_HARDWARE_ONLY) {
+		if (!flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			PERROR
+			    ("Hardware ME_IO_STREAM_CONFIG_HARDWARE_ONLY has to be with ME_IO_STREAM_CONFIG_WRAPAROUND.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if ((trigger->iAcqStopTrigType != ME_TRIG_TYPE_NONE)
+		    || (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE)) {
+			PERROR
+			    ("Hardware wraparound mode must be in infinite mode.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+	}
+
+	if (count != 1) {
+		PERROR("Only 1 entry in config list acceptable.\n");
+		return ME_ERRNO_INVALID_CONFIG_LIST_COUNT;
+	}
+
+	if (config_list[0].iChannel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (config_list[0].iStreamConfig != 0) {
+		PERROR("Only one range available.\n");
+		return ME_ERRNO_INVALID_STREAM_CONFIG;
+	}
+
+	if (config_list[0].iRef != ME_REF_AO_GROUND) {
+		PERROR("Output is referenced to ground.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if ((trigger->iAcqStartTicksLow != 0)
+	    || (trigger->iAcqStartTicksHigh != 0)) {
+		PERROR
+		    ("Invalid acquisition start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_ARG;
+	}
+
+	if (config_list[0].iFlags) {
+		PERROR("Invalid config list flag.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	switch (trigger->iAcqStartTrigType) {
+	case ME_TRIG_TYPE_SW:
+		if (trigger->iAcqStartTrigEdge != ME_TRIG_EDGE_NONE) {
+			PERROR
+			    ("Invalid acquisition start trigger edge specified.\n");
+			return ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+		}
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_ANY:
+		case ME_TRIG_EDGE_RISING:
+		case ME_TRIG_EDGE_FALLING:
+			break;
+
+		default:
+			PERROR
+			    ("Invalid acquisition start trigger edge specified.\n");
+			return ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+		}
+		break;
+
+	default:
+		PERROR("Invalid acquisition start trigger type specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_TRIG_TYPE;
+	}
+
+	if (trigger->iScanStartTrigType != ME_TRIG_TYPE_FOLLOW) {
+		PERROR("Invalid scan start trigger type specified.\n");
+		return ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+	}
+
+	if (trigger->iConvStartTrigType != ME_TRIG_TYPE_TIMER) {
+		PERROR("Invalid conv start trigger type specified.\n");
+		return ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+	}
+
+	if ((conv_ticks < ME4600_AO_MIN_CHAN_TICKS)
+	    || (conv_ticks > ME4600_AO_MAX_CHAN_TICKS)) {
+		PERROR("Invalid conv start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_CONV_START_ARG;
+	}
+
+	if (trigger->iAcqStartTicksLow || trigger->iAcqStartTicksHigh) {
+		PERROR("Invalid acq start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_ARG;
+	}
+
+	if (trigger->iScanStartTicksLow || trigger->iScanStartTicksHigh) {
+		PERROR("Invalid scan start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_SCAN_START_ARG;
+	}
+
+	switch (trigger->iScanStopTrigType) {
+	case ME_TRIG_TYPE_NONE:
+		if (trigger->iScanStopCount != 0) {
+			PERROR("Invalid scan stop count specified.\n");
+			return ME_ERRNO_INVALID_SCAN_STOP_ARG;
+		}
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iScanStopCount <= 0) {
+				PERROR("Invalid scan stop count specified.\n");
+				return ME_ERRNO_INVALID_SCAN_STOP_ARG;
+			}
+		} else {
+			PERROR("The continous mode has not 'scan' contects.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		}
+		break;
+
+	default:
+		PERROR("Invalid scan stop trigger type specified.\n");
+		return ME_ERRNO_INVALID_SCAN_STOP_TRIG_TYPE;
+	}
+
+	switch (trigger->iAcqStopTrigType) {
+	case ME_TRIG_TYPE_NONE:
+		if (trigger->iAcqStopCount != 0) {
+			PERROR("Invalid acq stop count specified.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_ARG;
+		}
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		}
+
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iAcqStopCount <= 0) {
+				PERROR
+				    ("The continous mode has not 'scan' contects.\n");
+				return ME_ERRNO_INVALID_ACQ_STOP_ARG;
+			}
+		}
+		break;
+
+	default:
+		PERROR("Invalid acq stop trigger type specified.\n");
+		return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+	}
+
+	switch (trigger->iAcqStartTrigChan) {
+	case ME_TRIG_CHAN_DEFAULT:
+	case ME_TRIG_CHAN_SYNCHRONOUS:
+		break;
+
+	default:
+		PERROR("Invalid acq start trigger channel specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_TRIG_CHAN;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if ((flags & ME_IO_STREAM_CONFIG_BIT_PATTERN) && !instance->bitpattern) {
+		PERROR("This subdevice not support output redirection.\n");
+		ME_SUBDEVICE_EXIT;
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+	//Stop device
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	//Check if state machine is stopped.
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	//Reset control register. Block all actions. Disable IRQ. Disable FIFO.
+	ctrl =
+	    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP | ME4600_AO_CTRL_BIT_STOP |
+	    ME4600_AO_CTRL_BIT_RESET_IRQ;
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+
+	//This is paranoic, but to be sure.
+	instance->preloaded_count = 0;
+	instance->data_count = 0;
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+
+	/* Set mode. */
+	if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {	//Wraparound
+		if (flags & ME_IO_STREAM_CONFIG_HARDWARE_ONLY) {	//Hardware wraparound
+			PINFO("Hardware wraparound.\n");
+			ctrl |= ME4600_AO_MODE_WRAPAROUND;
+			instance->mode = ME4600_AO_HW_WRAP_MODE;
+		} else {	//Software wraparound
+			PINFO("Software wraparound.\n");
+			ctrl |= ME4600_AO_MODE_CONTINUOUS;
+			instance->mode = ME4600_AO_SW_WRAP_MODE;
+		}
+	} else {		//Continous
+		PINFO("Continous.\n");
+		ctrl |= ME4600_AO_MODE_CONTINUOUS;
+		instance->mode = ME4600_AO_CONTINOUS;
+	}
+
+	//Set the trigger edge.
+	if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_EXT_DIGITAL) {	//Set the trigger type and edge for external trigger.
+		PINFO("External digital trigger.\n");
+		instance->start_mode = ME4600_AO_EXT_TRIG;
+/*
+			ctrl |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+*/
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_RISING:
+			PINFO("Set the trigger edge: rising.\n");
+			instance->ctrl_trg = 0x0;
+			break;
+
+		case ME_TRIG_EDGE_FALLING:
+			PINFO("Set the trigger edge: falling.\n");
+//                                      ctrl |= ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+			instance->ctrl_trg = ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+			break;
+
+		case ME_TRIG_EDGE_ANY:
+			PINFO("Set the trigger edge: both edges.\n");
+//                                      ctrl |= ME4600_AO_CTRL_BIT_EX_TRIG_EDGE | ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			instance->ctrl_trg =
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			break;
+		}
+	} else {
+		PINFO("Internal software trigger.\n");
+		instance->start_mode = 0;
+	}
+
+	//Set the stop mode and value.
+	if (trigger->iAcqStopTrigType == ME_TRIG_TYPE_COUNT) {	//Amount of data
+		instance->stop_mode = ME4600_AO_ACQ_STOP_MODE;
+		instance->stop_count = trigger->iAcqStopCount;
+	} else if (trigger->iScanStopTrigType == ME_TRIG_TYPE_COUNT) {	//Amount of 'scans'
+		instance->stop_mode = ME4600_AO_SCAN_STOP_MODE;
+		instance->stop_count = trigger->iScanStopCount;
+	} else {		//Infinite
+		instance->stop_mode = ME4600_AO_INF_STOP_MODE;
+		instance->stop_count = 0;
+	}
+
+	PINFO("Stop count: %d.\n", instance->stop_count);
+
+	if (trigger->iAcqStartTrigChan == ME_TRIG_CHAN_SYNCHRONOUS) {	//Synchronous start
+		instance->start_mode |= ME4600_AO_SYNC_HOLD;
+		if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_EXT_DIGITAL) {	//Externaly triggered
+			PINFO("Synchronous start. Externaly trigger active.\n");
+			instance->start_mode |= ME4600_AO_SYNC_EXT_TRIG;
+		}
+#ifdef MEDEBUG_INFO
+		else {
+			PINFO
+			    ("Synchronous start. Externaly trigger dissabled.\n");
+		}
+#endif
+
+	}
+	//Set speed
+	outl(conv_ticks - 2, instance->timer_reg);
+	PDEBUG_REG("timer_reg outl(0x%lX+0x%lX)=0x%llx\n", instance->reg_base,
+		   instance->timer_reg - instance->reg_base, conv_ticks - 2);
+	instance->hardware_stop_delay = (int)(conv_ticks * HZ) / ME4600_AO_BASE_FREQUENCY;	//<== MUST be with cast!
+
+	//Conect outputs to analog or digital port.
+	if (flags & ME_IO_STREAM_CONFIG_BIT_PATTERN) {
+		ctrl |= ME4600_AO_CTRL_BIT_ENABLE_DO;
+	}
+	// Write the control word
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+
+	//Set status.
+	instance->status = ao_status_stream_configured;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	long j;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!instance->circ_buf.buf) {
+		PERROR("Circular buffer not exists.\n");
+		return ME_ERRNO_INTERNAL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (me_circ_buf_space(&instance->circ_buf)) {	//The buffer is NOT full.
+		*count = me_circ_buf_space(&instance->circ_buf);
+	} else {		//The buffer is full.
+		if (time_out) {
+			t = (time_out * HZ) / 1000;
+
+			if (t == 0)
+				t = 1;
+		} else {	//Max time.
+			t = LONG_MAX;
+		}
+
+		*count = 0;
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Interrupts are when FIFO HF is signaled.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((me_circ_buf_space
+						   (&instance->circ_buf))
+						  || !(inl(instance->status_reg)
+						       &
+						       ME4600_AO_STATUS_BIT_FSM)),
+						 t);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PERROR("AO subdevice is not running.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+		} else if (signal_pending(current)) {
+			PERROR("Wait on values interrupted from signal.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		} else if ((jiffies - j) >= t) {
+			PERROR("Wait on values timed out.\n");
+			err = ME_ERRNO_TIMEOUT;
+		} else {	//Uff... all is good. Inform user about empty space.
+			*count = me_circ_buf_space(&instance->circ_buf);
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags = 0;
+	uint32_t status;
+	uint32_t ctrl;
+	uint32_t synch;
+	int count = 0;
+	int circ_buffer_count;
+
+	unsigned long ref;
+	unsigned long delay = 0;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags & ~ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+		PERROR("Invalid flags.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if ((start_mode != ME_START_MODE_BLOCKING)
+	    && (start_mode != ME_START_MODE_NONBLOCKING)) {
+		PERROR("Invalid start mode specified.\n");
+		return ME_ERRNO_INVALID_START_MODE;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+		if (delay == 0)
+			delay = 1;
+	}
+
+	switch (instance->status) {	//Checking actual mode.
+	case ao_status_stream_configured:
+	case ao_status_stream_end:
+		//Correct modes!
+		break;
+
+		//The device is in wrong mode.
+	case ao_status_none:
+	case ao_status_single_configured:
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+		PERROR
+		    ("Subdevice must be preinitialize correctly for streaming.\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		PDEBUG("Before restart broke stream 'STOP' must be caled.\n");
+		return ME_STATUS_ERROR;
+
+	case ao_status_stream_run_wait:
+	case ao_status_stream_run:
+	case ao_status_stream_end_wait:
+		PDEBUG("Stream is already working.\n");
+		return ME_ERRNO_SUBDEVICE_BUSY;
+
+	default:
+		instance->status = ao_status_stream_error;
+		PERROR_CRITICAL("Status is in wrong state!\n");
+		return ME_ERRNO_INTERNAL;
+
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->mode == ME4600_AO_CONTINOUS) {	//Continous
+		instance->circ_buf.tail += instance->preloaded_count;
+		instance->circ_buf.tail &= instance->circ_buf.mask;
+	}
+	circ_buffer_count = me_circ_buf_values(&instance->circ_buf);
+
+	if (!circ_buffer_count && !instance->preloaded_count) {	//No values in buffer
+		ME_SUBDEVICE_EXIT;
+		PERROR("No values in buffer!\n");
+		return ME_ERRNO_LACK_OF_RESOURCES;
+	}
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+	//Set values for single_read()
+	instance->single_value = ME4600_AO_MAX_DATA + 1;
+	instance->single_value_in_fifo = ME4600_AO_MAX_DATA + 1;
+
+	//Setting stop points
+	if (instance->stop_mode == ME4600_AO_SCAN_STOP_MODE) {
+		instance->stop_data_count =
+		    instance->stop_count * circ_buffer_count;
+	} else {
+		instance->stop_data_count = instance->stop_count;
+	}
+
+	if ((instance->stop_data_count != 0)
+	    && (instance->stop_data_count < circ_buffer_count)) {
+		PERROR("More data in buffer than previously set limit!\n");
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	ctrl = inl(instance->ctrl_reg);
+	//Check FIFO
+	if (!(ctrl & ME4600_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO wasn't enabeled. Do it. <= This should be done by user call with ME_WRITE_MODE_PRELOAD
+		PINFO("Enableing FIFO.\n");
+		ctrl |=
+		    ME4600_AO_CTRL_BIT_ENABLE_FIFO |
+		    ME4600_AO_CTRL_BIT_RESET_IRQ;
+
+		instance->preloaded_count = 0;
+		instance->data_count = 0;
+	} else {		//Block IRQ
+		ctrl |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base,
+		   ctrl | ME4600_AO_CTRL_BIT_RESET_IRQ);
+
+	//Fill FIFO <= Generaly this should be done by user pre-load call but this is second place to do it.
+	status = inl(instance->status_reg);
+	if (!(status & ME4600_AO_STATUS_BIT_EF)) {	//FIFO empty
+		if (instance->stop_data_count == 0) {
+			count = ME4600_AO_FIFO_COUNT;
+		} else {
+			count =
+			    (ME4600_AO_FIFO_COUNT <
+			     instance->
+			     stop_data_count) ? ME4600_AO_FIFO_COUNT :
+			    instance->stop_data_count;
+		}
+
+		//Copy data
+		count =
+		    ao_write_data(instance, count, instance->preloaded_count);
+
+		if (count < 0) {	//This should never happend!
+			PERROR_CRITICAL("COPY FINISH WITH ERROR!\n");
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+	}
+	//Set pre-load features.
+	spin_lock(instance->preload_reg_lock);
+	synch = inl(instance->preload_reg);
+	synch &=
+	    ~((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+	synch |=
+	    (instance->start_mode & ~ME4600_AO_EXT_TRIG) << instance->ao_idx;
+	outl(synch, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, synch);
+	spin_unlock(instance->preload_reg_lock);
+
+	//Default count is '0'
+	if (instance->mode == ME4600_AO_CONTINOUS) {	//Continous
+		instance->preloaded_count = 0;
+		instance->circ_buf.tail += count;
+		instance->circ_buf.tail &= instance->circ_buf.mask;
+	} else {		//Wraparound
+		instance->preloaded_count += count;
+		instance->data_count += count;
+
+		//Special case: Infinite wraparound with less than FIFO datas always should runs in hardware mode.
+		if ((instance->stop_mode == ME4600_AO_INF_STOP_MODE)
+		    && (circ_buffer_count <= ME4600_AO_FIFO_COUNT)) {	//Change to hardware wraparound
+			PDEBUG
+			    ("Changeing mode from software wraparound to hardware wraparound.\n");
+			//Copy all data
+			count =
+			    ao_write_data(instance, circ_buffer_count,
+					  instance->preloaded_count);
+			ctrl &= ~ME4600_AO_CTRL_MODE_MASK;
+			ctrl |= ME4600_AO_MODE_WRAPAROUND;
+		}
+
+		if (instance->preloaded_count == me_circ_buf_values(&instance->circ_buf)) {	//Reset position indicator.
+			instance->preloaded_count = 0;
+		} else if (instance->preloaded_count > me_circ_buf_values(&instance->circ_buf)) {	//This should never happend!
+			PERROR_CRITICAL
+			    ("PRELOADED MORE VALUES THAN ARE IN BUFFER!\n");
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+	}
+
+	//Set status to 'wait for start'
+	instance->status = ao_status_stream_run_wait;
+
+	status = inl(instance->status_reg);
+	//Start state machine and interrupts
+	ctrl &= ~(ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+	if (instance->start_mode == ME4600_AO_EXT_TRIG) {	// External trigger.
+		PINFO("External trigger.\n");
+		ctrl |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+	}
+	if (!(status & ME4600_AO_STATUS_BIT_HF)) {	//More than half!
+		if ((ctrl & ME4600_AO_CTRL_MODE_MASK) == ME4600_AO_MODE_CONTINUOUS) {	//Enable IRQ only when hardware_continous is set and FIFO is more than half
+			ctrl &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+			ctrl |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		}
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	//Trigger output
+	if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Trigger outputs
+		spin_lock(instance->preload_reg_lock);
+		synch = inl(instance->preload_reg);
+		//Add channel to start list
+		outl(synch | (ME4600_AO_SYNC_HOLD << instance->ao_idx),
+		     instance->preload_reg);
+		PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->preload_reg - instance->reg_base,
+			   synch | (ME4600_AO_SYNC_HOLD << instance->ao_idx));
+
+		//Fire
+		PINFO
+		    ("Fired all software synchronous outputs by software trigger.\n");
+		outl(0x8000, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, 0x8000);
+
+		//Restore save settings
+		outl(synch, instance->preload_reg);
+		PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->preload_reg - instance->reg_base, synch);
+		spin_unlock(instance->preload_reg_lock);
+	} else if (!instance->start_mode) {	//Trigger outputs
+/*
+		//Remove channel from start list.	// <== Unnecessary. Removed.
+		spin_lock(instance->preload_reg_lock);
+			synch = inl(instance->preload_reg);
+			outl(synch & ~(ME4600_AO_SYNC_HOLD << instance->ao_idx), instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, synch & ~(ME4600_AO_SYNC_HOLD << instance->ao_idx));
+*/
+		//Fire
+		PINFO("Software trigger.\n");
+		outl(0x8000, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, 0x8000);
+
+/*
+			//Restore save settings.	// <== Unnecessary. Removed.
+			outl(synch, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, synch);
+		spin_unlock(instance->preload_reg_lock);
+*/
+	}
+	// Set control task's timeout
+	ref = jiffies;
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = ref;
+
+	if (status & ME4600_AO_STATUS_BIT_HF) {	//Less than half but not empty!
+		PINFO("Less than half.\n");
+		if (instance->stop_data_count != 0) {
+			count = ME4600_AO_FIFO_COUNT / 2;
+		} else {
+			count =
+			    ((ME4600_AO_FIFO_COUNT / 2) <
+			     instance->stop_data_count) ? ME4600_AO_FIFO_COUNT /
+			    2 : instance->stop_data_count;
+		}
+
+		//Copy data
+		count =
+		    ao_write_data(instance, count, instance->preloaded_count);
+
+		if (count < 0) {	//This should never happend!
+			PERROR_CRITICAL("COPY FINISH WITH ERROR!\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+
+		if (instance->mode == ME4600_AO_CONTINOUS) {	//Continous
+			instance->circ_buf.tail += count;
+			instance->circ_buf.tail &= instance->circ_buf.mask;
+		} else {	//Wraparound
+			instance->data_count += count;
+			instance->preloaded_count += count;
+
+			if (instance->preloaded_count == me_circ_buf_values(&instance->circ_buf)) {	//Reset position indicator.
+				instance->preloaded_count = 0;
+			} else if (instance->preloaded_count > me_circ_buf_values(&instance->circ_buf)) {	//This should never happend!
+				PERROR_CRITICAL
+				    ("PRELOADED MORE VALUES THAN ARE IN BUFFER!\n");
+				ME_SUBDEVICE_EXIT;
+				return ME_ERRNO_INTERNAL;
+			}
+		}
+
+		status = inl(instance->status_reg);
+		if (!(status & ME4600_AO_STATUS_BIT_HF)) {	//More than half!
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+			ctrl |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+	}
+	//Special case: Limited wraparound with less than HALF FIFO datas need work around to generate first interrupt.
+	if ((instance->stop_mode != ME4600_AO_INF_STOP_MODE)
+	    && (instance->mode == ME4600_AO_SW_WRAP_MODE)
+	    && (circ_buffer_count <= (ME4600_AO_FIFO_COUNT / 2))) {	//Put more data to FIFO
+		PINFO("Limited wraparound with less than HALF FIFO datas.\n");
+		if (instance->preloaded_count) {	//This should never happend!
+			PERROR_CRITICAL
+			    ("ERROR WHEN LOADING VALUES FOR WRAPAROUND!\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+
+		while (instance->stop_data_count > instance->data_count) {	//Maximum data not set jet.
+			//Copy to buffer
+			if (circ_buffer_count != ao_write_data(instance, circ_buffer_count, 0)) {	//This should never happend!
+				PERROR_CRITICAL
+				    ("ERROR WHEN LOADING VALUES FOR WRAPAROUND!\n");
+				ME_SUBDEVICE_EXIT;
+				return ME_ERRNO_INTERNAL;
+			}
+			instance->data_count += circ_buffer_count;
+
+			if (!((status = inl(instance->status_reg)) & ME4600_AO_STATUS_BIT_HF)) {	//FIFO is more than half. Enable IRQ and end copy.
+				spin_lock_irqsave(&instance->subdevice_lock,
+						  cpu_flags);
+				ctrl = inl(instance->ctrl_reg);
+				ctrl &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+				ctrl |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+				outl(ctrl, instance->ctrl_reg);
+				PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->ctrl_reg -
+					   instance->reg_base, ctrl);
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				break;
+			}
+		}
+	}
+	// Schedule control task.
+	instance->ao_control_task_flag = 1;
+	queue_delayed_work(instance->me4600_workqueue,
+			   &instance->ao_control_task, 1);
+
+	if (start_mode == ME_START_MODE_BLOCKING) {	//Wait for start.
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_stream_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if ((instance->status != ao_status_stream_run)
+		    && (instance->status != ao_status_stream_end)) {
+			PDEBUG("Starting stream canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		} else if ((delay) && ((jiffies - ref) >= delay)) {
+			if (instance->status != ao_status_stream_run) {
+				if (instance->status == ao_status_stream_end) {
+					PDEBUG("Timeout reached.\n");
+				} else {
+					if ((jiffies - ref) > delay) {
+						PERROR
+						    ("Timeout reached. Not handled by control task!\n");
+					} else {
+						PERROR
+						    ("Timeout reached. Signal come but status is strange: %d\n",
+						     instance->status);
+					}
+					ao_stop_immediately(instance);
+				}
+
+				instance->ao_control_task_flag = 0;
+				cancel_delayed_work(&instance->ao_control_task);
+				instance->status = ao_status_stream_end;
+				err = ME_ERRNO_TIMEOUT;
+			}
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+	return err;
+}
+
+static int me4600_ao_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((wait != ME_WAIT_NONE) && (wait != ME_WAIT_IDLE)) {
+		PERROR("Invalid wait argument specified.\n");
+		*status = ME_STATUS_INVALID;
+		return ME_ERRNO_INVALID_WAIT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	switch (instance->status) {
+	case ao_status_single_configured:
+	case ao_status_single_end:
+	case ao_status_stream_configured:
+	case ao_status_stream_end:
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		*status = ME_STATUS_IDLE;
+		break;
+
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+	case ao_status_stream_run_wait:
+	case ao_status_stream_run:
+	case ao_status_stream_end_wait:
+		*status = ME_STATUS_BUSY;
+		break;
+
+	case ao_status_none:
+	default:
+		*status =
+		    (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) ?
+		    ME_STATUS_BUSY : ME_STATUS_IDLE;
+		break;
+	}
+
+	if ((wait == ME_WAIT_IDLE) && (*status == ME_STATUS_BUSY)) {
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((instance->status !=
+						   ao_status_single_run_wait)
+						  && (instance->status !=
+						      ao_status_single_run)
+						  && (instance->status !=
+						      ao_status_single_end_wait)
+						  && (instance->status !=
+						      ao_status_stream_run_wait)
+						  && (instance->status !=
+						      ao_status_stream_run)
+						  && (instance->status !=
+						      ao_status_stream_end_wait)),
+						 LONG_MAX);
+
+		if (instance->status != ao_status_stream_end) {
+			PDEBUG("Wait for IDLE canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait for IDLE interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		*status = ME_STATUS_IDLE;
+	}
+
+	*values = me_circ_buf_space(&instance->circ_buf);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags)
+{				// Stop work and empty buffer and FIFO
+	int err = ME_ERRNO_SUCCESS;
+	me4600_ao_subdevice_t *instance;
+	unsigned long cpu_flags;
+	volatile uint32_t ctrl;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags & ~ME_IO_STREAM_STOP_PRESERVE_BUFFERS) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((stop_mode != ME_STOP_MODE_IMMEDIATE)
+	    && (stop_mode != ME_STOP_MODE_LAST_VALUE)) {
+		PERROR("Invalid stop mode specified.\n");
+		return ME_ERRNO_INVALID_STOP_MODE;
+	}
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (instance->status < ao_status_stream_configured) {
+		//There is nothing to stop!
+		PERROR("Subdevice not in streaming mode. %d\n",
+		       instance->status);
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Mark as stopping. => Software stop.
+	instance->status = ao_status_stream_end_wait;
+
+	if (stop_mode == ME_STOP_MODE_IMMEDIATE) {	//Stopped now!
+		err = ao_stop_immediately(instance);
+	} else if (stop_mode == ME_STOP_MODE_LAST_VALUE) {
+		ctrl = inl(instance->ctrl_reg) & ME4600_AO_CTRL_MODE_MASK;
+		if (ctrl == ME4600_AO_MODE_WRAPAROUND) {	//Hardware wraparound => Hardware stop.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME4600_AO_CTRL_BIT_STOP |
+			    ME4600_AO_CTRL_BIT_RESET_IRQ;
+			ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+		//Only runing process will interrupt this call. Events are signaled when status change.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_stream_end_wait),
+						 LONG_MAX);
+
+		if (instance->status != ao_status_stream_end) {
+			PDEBUG("Stopping stream canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Stopping stream interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	ctrl = inl(instance->ctrl_reg);
+	ctrl |=
+	    ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP |
+	    ME4600_AO_CTRL_BIT_RESET_IRQ;
+	ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+	if (!flags) {		//Reset FIFO
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	if (!flags) {		//Reset software buffer
+		instance->circ_buf.head = 0;
+		instance->circ_buf.tail = 0;
+		instance->preloaded_count = 0;
+		instance->data_count = 0;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int write_mode,
+				     int *values, int *count, int flags)
+{
+	int err = ME_ERRNO_SUCCESS;
+	me4600_ao_subdevice_t *instance;
+	unsigned long cpu_flags = 0;
+	uint32_t reg_copy;
+
+	int copied_from_user = 0;
+	int left_to_copy_from_user = *count;
+
+	int copied_values;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	//Checking arguments
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (*count <= 0) {
+		PERROR("Invalid count of values specified.\n");
+		return ME_ERRNO_INVALID_VALUE_COUNT;
+	}
+
+	if (values == NULL) {
+		PERROR("Invalid address of values specified.\n");
+		return ME_ERRNO_INVALID_POINTER;
+	}
+
+	if ((instance->status == ao_status_none) || (instance->status == ao_status_single_configured)) {	//The device is in single mode.
+		PERROR
+		    ("Subdevice must be preinitialize correctly for streaming.\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+/// @note If no 'pre-load' is used. stream_start() will move data to FIFO.
+	switch (write_mode) {
+	case ME_WRITE_MODE_PRELOAD:
+
+		//Device must be stopped.
+		if ((instance->status != ao_status_stream_configured)
+		    && (instance->status != ao_status_stream_end)) {
+			PERROR
+			    ("Subdevice mustn't be runing when 'pre-load' mode is used.\n");
+			return ME_ERRNO_PREVIOUS_CONFIG;
+		}
+		break;
+	case ME_WRITE_MODE_NONBLOCKING:
+	case ME_WRITE_MODE_BLOCKING:
+		/// @note In blocking mode: When device is not runing and there is not enought space call will blocked up!
+		/// @note Some other thread must empty buffer by starting engine.
+		break;
+
+	default:
+		PERROR("Invalid write mode specified.\n");
+		return ME_ERRNO_INVALID_WRITE_MODE;
+	}
+
+	if (instance->mode & ME4600_AO_WRAP_MODE) {	//Wraparound mode. Device must be stopped.
+		if ((instance->status != ao_status_stream_configured)
+		    && (instance->status != ao_status_stream_end)) {
+			PERROR
+			    ("Subdevice mustn't be runing when 'pre-load' mode is used.\n");
+			return ME_ERRNO_INVALID_WRITE_MODE;
+		}
+	}
+
+	if ((instance->mode == ME4600_AO_HW_WRAP_MODE) && (write_mode != ME_WRITE_MODE_PRELOAD)) {	// hardware wrap_around mode.
+		//This is transparent for user.
+		PDEBUG("Changing write_mode to ME_WRITE_MODE_PRELOAD.\n");
+		write_mode = ME_WRITE_MODE_PRELOAD;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (write_mode == ME_WRITE_MODE_PRELOAD) {	//Init enviroment - preload
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		reg_copy = inl(instance->ctrl_reg);
+		//Check FIFO
+		if (!(reg_copy & ME4600_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO not active. Enable it.
+			reg_copy |= ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			instance->preloaded_count = 0;
+		}
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	}
+
+	while (1) {
+		//Copy to buffer. This step is common for all modes.
+		copied_from_user =
+		    ao_get_data_from_user(instance, left_to_copy_from_user,
+					  values + (*count -
+						    left_to_copy_from_user));
+		left_to_copy_from_user -= copied_from_user;
+
+		reg_copy = inl(instance->status_reg);
+		if ((instance->status == ao_status_stream_run) && !(reg_copy & ME4600_AO_STATUS_BIT_FSM)) {	//BROKEN PIPE! The state machine is stoped but logical status show that should be working.
+			PERROR("Broken pipe in write.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+			break;
+		}
+
+		if ((instance->status == ao_status_stream_run) && (instance->mode == ME4600_AO_CONTINOUS) && (reg_copy & ME4600_AO_STATUS_BIT_HF)) {	//Continous mode runing and data are below half!
+
+			// Block interrupts.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			reg_copy = inl(instance->ctrl_reg);
+			//reg_copy &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			reg_copy |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			//Fast copy
+			copied_values =
+			    ao_write_data(instance, ME4600_AO_FIFO_COUNT / 2,
+					  0);
+			if (copied_values > 0) {
+				instance->circ_buf.tail += copied_values;
+				instance->circ_buf.tail &=
+				    instance->circ_buf.mask;
+				continue;
+			}
+			// Activate interrupts.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			reg_copy = inl(instance->ctrl_reg);
+			//reg_copy |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			reg_copy &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (copied_values == 0) {	//This was checked and never should happend!
+				PERROR_CRITICAL("COPING FINISH WITH 0!\n");
+			}
+
+			if (copied_values < 0) {	//This was checked and never should happend!
+				PERROR_CRITICAL
+				    ("COPING FINISH WITH AN ERROR!\n");
+				instance->status = ao_status_stream_fifo_error;
+				err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+				break;
+			}
+		}
+
+		if (!left_to_copy_from_user) {	//All datas were copied.
+			break;
+		} else {	//Not all datas were copied.
+			if (instance->mode & ME4600_AO_WRAP_MODE) {	//Error too much datas! Wraparound is limited in size!
+				PERROR
+				    ("Too much data for wraparound mode!  Exceeded size of %ld.\n",
+				     ME4600_AO_CIRC_BUF_COUNT - 1);
+				err = ME_ERRNO_RING_BUFFER_OVERFLOW;
+				break;
+			}
+
+			if (write_mode != ME_WRITE_MODE_BLOCKING) {	//Non blocking calls
+				break;
+			}
+
+			wait_event_interruptible(instance->wait_queue,
+						 me_circ_buf_space(&instance->
+								   circ_buf));
+
+			if (signal_pending(current)) {
+				PERROR("Writing interrupted by signal.\n");
+				instance->status = ao_status_none;
+				ao_stop_immediately(instance);
+				err = ME_ERRNO_SIGNAL;
+				break;
+			}
+
+			if (instance->status == ao_status_none) {	//Reset
+				PERROR("Writing interrupted by reset.\n");
+				err = ME_ERRNO_CANCELLED;
+				break;
+			}
+		}
+	}
+
+	if (write_mode == ME_WRITE_MODE_PRELOAD) {	//Copy data to FIFO - preload
+		copied_values =
+		    ao_write_data_pooling(instance, ME4600_AO_FIFO_COUNT,
+					  instance->preloaded_count);
+		instance->preloaded_count += copied_values;
+		instance->data_count += copied_values;
+
+		if ((instance->mode == ME4600_AO_HW_WRAP_MODE)
+		    && (me_circ_buf_values(&instance->circ_buf) >
+			ME4600_AO_FIFO_COUNT)) {
+			PERROR
+			    ("Too much data for hardware wraparound mode! Exceeded size of %d.\n",
+			     ME4600_AO_FIFO_COUNT);
+			err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+		}
+	}
+
+	*count = *count - left_to_copy_from_user;
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+static irqreturn_t me4600_ao_isr(int irq, void *dev_id
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+				 , struct pt_regs *regs
+#endif
+    )
+{
+	me4600_ao_subdevice_t *instance = dev_id;
+	uint32_t irq_status;
+	uint32_t ctrl;
+	uint32_t status;
+	int count = 0;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inl(instance->irq_status_reg);
+	if (!(irq_status & (ME4600_IRQ_STATUS_BIT_AO_HF << instance->ao_idx))) {
+		PINFO("%ld Shared interrupt. %s(): ID=%d: status_reg=0x%04X\n",
+		      jiffies, __FUNCTION__, instance->ao_idx, irq_status);
+		return IRQ_NONE;
+	}
+
+	if (!instance->circ_buf.buf) {
+		instance->status = ao_status_stream_error;
+		PERROR_CRITICAL("CIRCULAR BUFFER NOT EXISTS!\n");
+		//Block interrupts. Stop machine.
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		ctrl |=
+		    ME4600_AO_CTRL_BIT_RESET_IRQ |
+		    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP | ME4600_AO_CTRL_BIT_STOP;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		//Inform user
+		wake_up_interruptible_all(&instance->wait_queue);
+		return IRQ_HANDLED;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME4600_AO_STATUS_BIT_FSM)) {	//Too late. Not working! END? BROKEN PIPE?
+		PDEBUG("Interrupt come but ISM is not working!\n");
+		//Block interrupts. Stop machine.
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		ctrl |=
+		    ME4600_AO_CTRL_BIT_RESET_IRQ | ME4600_AO_CTRL_BIT_STOP |
+		    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		return IRQ_HANDLED;
+	}
+	//General procedure. Process more datas.
+
+#ifdef MEDEBUG_DEBUG
+	if (!me_circ_buf_values(&instance->circ_buf)) {	//Buffer is empty!
+		PDEBUG("Circular buffer empty!\n");
+	}
+#endif
+
+	//Check FIFO
+	if (status & ME4600_AO_STATUS_BIT_HF) {	//OK less than half
+
+		//Block interrupts
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		ctrl |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		do {
+			//Calculate how many should be copied.
+			count =
+			    (instance->stop_data_count) ? instance->
+			    stop_data_count -
+			    instance->data_count : ME4600_AO_FIFO_COUNT / 2;
+			if (ME4600_AO_FIFO_COUNT / 2 < count) {
+				count = ME4600_AO_FIFO_COUNT / 2;
+			}
+			//Copy data
+			if (instance->mode == ME4600_AO_CONTINOUS) {	//Continous
+				count = ao_write_data(instance, count, 0);
+				if (count > 0) {
+					instance->circ_buf.tail += count;
+					instance->circ_buf.tail &=
+					    instance->circ_buf.mask;
+					instance->data_count += count;
+
+					if ((instance->status == ao_status_stream_end_wait) && !me_circ_buf_values(&instance->circ_buf)) {	//Stoping. Whole buffer was copied.
+						break;
+					}
+				}
+			} else if ((instance->mode == ME4600_AO_SW_WRAP_MODE) && ((ctrl & ME4600_AO_CTRL_MODE_MASK) == ME4600_AO_MODE_CONTINUOUS)) {	//Wraparound (software)
+				if (instance->status == ao_status_stream_end_wait) {	//We stoping => Copy to the end of the buffer.
+					count =
+					    ao_write_data(instance, count, 0);
+				} else {	//Copy in wraparound mode.
+					count =
+					    ao_write_data_wraparound(instance,
+								     count,
+								     instance->
+								     preloaded_count);
+				}
+
+				if (count > 0) {
+					instance->data_count += count;
+					instance->preloaded_count += count;
+					instance->preloaded_count %=
+					    me_circ_buf_values(&instance->
+							       circ_buf);
+
+					if ((instance->status == ao_status_stream_end_wait) && !instance->preloaded_count) {	//Stoping. Whole buffer was copied.
+						break;
+					}
+				}
+			}
+
+			if ((count <= 0) || (instance->stop_data_count && (instance->stop_data_count <= instance->data_count))) {	//End of work.
+				break;
+			}
+		}		//Repeat if still is under half fifo
+		while ((status =
+			inl(instance->status_reg)) & ME4600_AO_STATUS_BIT_HF);
+
+		//Unblock interrupts
+		ctrl = inl(instance->ctrl_reg);
+		if (count >= 0) {	//Copy was successful.
+			if (instance->stop_data_count && (instance->stop_data_count <= instance->data_count)) {	//Finishing work. No more interrupts.
+				PDEBUG("Finishing work. Interrupt disabled.\n");
+				instance->status = ao_status_stream_end_wait;
+			} else if (count > 0) {	//Normal work. Enable interrupt.
+				PDEBUG("Normal work. Enable interrupt.\n");
+				ctrl &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+				ctrl |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			} else {	//Normal work but there are no more data in buffer. Interrupt active but blocked. stream_write() will unblock it.
+				PDEBUG
+				    ("No data in software buffer. Interrupt blocked.\n");
+				ctrl |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			}
+		} else {	//Error during copy.
+			instance->status = ao_status_stream_fifo_error;
+		}
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+	} else {		//?? more than half
+		PDEBUG
+		    ("Interrupt come but FIFO more than half full! Reset interrupt.\n");
+		//Reset pending interrupt
+		ctrl = inl(instance->ctrl_reg);
+		ctrl |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		ctrl &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+	}
+
+	PINFO("ISR: Buffer count: %d.(T:%d H:%d)\n",
+	      me_circ_buf_values(&instance->circ_buf), instance->circ_buf.tail,
+	      instance->circ_buf.head);
+	PINFO("ISR: Stop count: %d.\n", instance->stop_count);
+	PINFO("ISR: Stop data count: %d.\n", instance->stop_data_count);
+	PINFO("ISR: Data count: %d.\n", instance->data_count);
+
+	//Inform user
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+static void me4600_ao_destructor(struct me_subdevice *subdevice)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	instance->ao_control_task_flag = 0;
+
+	// Reset subdevice to asure clean exit.
+	me4600_ao_io_reset_subdevice(subdevice, NULL,
+				     ME_IO_RESET_SUBDEVICE_NO_FLAGS);
+
+	// Remove any tasks from work queue. This is paranoic because it was done allready in reset().
+	if (!cancel_delayed_work(&instance->ao_control_task)) {	//Wait 2 ticks to be sure that control task is removed from queue.
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(2);
+	}
+
+	if (instance->fifo) {
+		if (instance->irq) {
+			free_irq(instance->irq, instance);
+			instance->irq = 0;
+		}
+
+		if (instance->circ_buf.buf) {
+			free_pages((unsigned long)instance->circ_buf.buf,
+				   ME4600_AO_CIRC_BUF_SIZE_ORDER);
+		}
+		instance->circ_buf.buf = NULL;
+	}
+
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+me4600_ao_subdevice_t *me4600_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     int ao_idx,
+					     int fifo,
+					     int irq,
+					     struct workqueue_struct *me4600_wq)
+{
+	me4600_ao_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed. idx=%d\n", ao_idx);
+
+	// Allocate memory for subdevice instance.
+	subdevice = kmalloc(sizeof(me4600_ao_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_ao_subdevice_t));
+
+	// Initialize subdevice base class.
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->preload_reg_lock = preload_reg_lock;
+	subdevice->preload_flags = preload_flags;
+
+	// Store analog output index.
+	subdevice->ao_idx = ao_idx;
+
+	// Store if analog output has fifo.
+	subdevice->fifo = (ao_idx < fifo) ? 1 : 0;
+
+	if (subdevice->fifo) {	// Allocate and initialize circular buffer.
+		subdevice->circ_buf.mask = ME4600_AO_CIRC_BUF_COUNT - 1;
+
+		subdevice->circ_buf.buf =
+		    (void *)__get_free_pages(GFP_KERNEL,
+					     ME4600_AO_CIRC_BUF_SIZE_ORDER);
+		PDEBUG("circ_buf = %p size=%ld\n", subdevice->circ_buf.buf,
+		       ME4600_AO_CIRC_BUF_SIZE);
+
+		if (!subdevice->circ_buf.buf) {
+			PERROR
+			    ("Cannot initialize subdevice base class instance.\n");
+			kfree(subdevice);
+			return NULL;
+		}
+
+		memset(subdevice->circ_buf.buf, 0, ME4600_AO_CIRC_BUF_SIZE);
+	} else {		// No FIFO.
+		subdevice->circ_buf.mask = 0;
+		subdevice->circ_buf.buf = NULL;
+	}
+
+	subdevice->circ_buf.head = 0;
+	subdevice->circ_buf.tail = 0;
+
+	subdevice->status = ao_status_none;
+	subdevice->ao_control_task_flag = 0;
+	subdevice->timeout.delay = 0;
+	subdevice->timeout.start_time = jiffies;
+
+	// Initialize wait queue.
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	// Initialize single value to 0V.
+	subdevice->single_value = 0x8000;
+	subdevice->single_value_in_fifo = 0x8000;
+
+	// Register interrupt service routine.
+	if (subdevice->fifo) {
+		subdevice->irq = irq;
+		if (request_irq(subdevice->irq, me4600_ao_isr,
+#ifdef IRQF_DISABLED
+				IRQF_DISABLED | IRQF_SHARED,
+#else
+				SA_INTERRUPT | SA_SHIRQ,
+#endif
+				ME4600_NAME, subdevice)) {
+			PERROR("Cannot get interrupt line.\n");
+			PDEBUG("free circ_buf = %p size=%d",
+			       subdevice->circ_buf.buf,
+			       PAGE_SHIFT << ME4600_AO_CIRC_BUF_SIZE_ORDER);
+			free_pages((unsigned long)subdevice->circ_buf.buf,
+				   ME4600_AO_CIRC_BUF_SIZE_ORDER);
+			me_subdevice_deinit((me_subdevice_t *) subdevice);
+			kfree(subdevice);
+			return NULL;
+		}
+		PINFO("Registered irq=%d.\n", subdevice->irq);
+	} else {
+		subdevice->irq = 0;
+	}
+
+	// Initialize registers.
+	subdevice->irq_status_reg = reg_base + ME4600_IRQ_STATUS_REG;
+	subdevice->preload_reg = reg_base + ME4600_AO_SYNC_REG;
+	if (ao_idx == 0) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_00_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_00_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_00_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_00_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_00_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bitpattern = 0;
+	} else if (ao_idx == 1) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_01_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_01_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_01_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_01_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_01_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bitpattern = 0;
+	} else if (ao_idx == 2) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_02_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_02_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_02_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_02_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_02_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bitpattern = 0;
+	} else if (ao_idx == 3) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_03_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_03_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_03_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_03_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_03_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bitpattern = 1;
+	} else {
+		PERROR_CRITICAL("WRONG SUBDEVICE idx=%d!", ao_idx);
+		me_subdevice_deinit((me_subdevice_t *) subdevice);
+		if (subdevice->fifo) {
+			free_pages((unsigned long)subdevice->circ_buf.buf,
+				   ME4600_AO_CIRC_BUF_SIZE_ORDER);
+		}
+		subdevice->circ_buf.buf = NULL;
+		kfree(subdevice);
+		return NULL;
+	}
+
+	// Override base class methods.
+	subdevice->base.me_subdevice_destructor = me4600_ao_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_ao_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_ao_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_ao_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me4600_ao_io_single_write;
+	subdevice->base.me_subdevice_io_stream_config =
+	    me4600_ao_io_stream_config;
+	subdevice->base.me_subdevice_io_stream_new_values =
+	    me4600_ao_io_stream_new_values;
+	subdevice->base.me_subdevice_io_stream_write =
+	    me4600_ao_io_stream_write;
+	subdevice->base.me_subdevice_io_stream_start =
+	    me4600_ao_io_stream_start;
+	subdevice->base.me_subdevice_io_stream_status =
+	    me4600_ao_io_stream_status;
+	subdevice->base.me_subdevice_io_stream_stop = me4600_ao_io_stream_stop;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_ao_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_ao_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_ao_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me4600_ao_query_subdevice_caps_args;
+	subdevice->base.me_subdevice_query_range_by_min_max =
+	    me4600_ao_query_range_by_min_max;
+	subdevice->base.me_subdevice_query_number_ranges =
+	    me4600_ao_query_number_ranges;
+	subdevice->base.me_subdevice_query_range_info =
+	    me4600_ao_query_range_info;
+	subdevice->base.me_subdevice_query_timer = me4600_ao_query_timer;
+
+	// Prepare work queue
+	subdevice->me4600_workqueue = me4600_wq;
+
+/* workqueue API changed in kernel 2.6.20 */
+#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) )
+	INIT_WORK(&subdevice->ao_control_task, me4600_ao_work_control_task,
+		  (void *)subdevice);
+#else
+	INIT_DELAYED_WORK(&subdevice->ao_control_task,
+			  me4600_ao_work_control_task);
+#endif
+
+	if (subdevice->fifo) {	// Set speed for single operations.
+		outl(ME4600_AO_MIN_CHAN_TICKS - 1, subdevice->timer_reg);
+		subdevice->hardware_stop_delay = HZ / 10;	//100ms
+	}
+
+	return subdevice;
+}
+
+/** @brief Stop presentation. Preserve FIFOs.
+*
+* @param instance The subdevice instance (pointer).
+*/
+int inline ao_stop_immediately(me4600_ao_subdevice_t * instance)
+{
+	unsigned long cpu_flags;
+	uint32_t ctrl;
+	int timeout;
+	int i;
+
+	timeout =
+	    (instance->hardware_stop_delay >
+	     (HZ / 10)) ? instance->hardware_stop_delay : HZ / 10;
+	for (i = 0; i <= timeout; i++) {
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		// Stop all actions. No conditions! Block interrupts. Leave FIFO untouched!
+		ctrl = inl(instance->ctrl_reg);
+		ctrl |=
+		    ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+		    | ME4600_AO_CTRL_BIT_RESET_IRQ;
+		ctrl &=
+		    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+		      ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG);
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {	// Exit.
+			break;
+		}
+		//Still working!
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	if (i > timeout) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		return ME_ERRNO_INTERNAL;
+	}
+	return ME_ERRNO_SUCCESS;
+}
+
+/** @brief Copy data from circular buffer to fifo (fast) in wraparound.
+* @note This is time critical function. Checking is done at begining and end only.
+* @note The is not reasonable way to check how many walues was in FIFO at begining. The count must be managed externaly.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied data.
+* @return On error/success: 0.	No datas were copied => no data in buffer.
+* @return On error: -ME_ERRNO_FIFO_BUFFER_OVERFLOW.
+*/
+int inline ao_write_data_wraparound(me4600_ao_subdevice_t * instance, int count,
+				    int start_pos)
+{				/// @note This is time critical function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int i = 1;
+
+	if (count <= 0) {	//Wrong count!
+		return 0;
+	}
+
+	while (i < local_count) {
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+		if (pos == instance->circ_buf.head) {
+			pos = instance->circ_buf.tail;
+		}
+		i++;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME4600_AO_STATUS_BIT_FF)) {	//FIFO is full before all datas were copied!
+		PERROR("FIFO was full before all datas were copied! idx=%d\n",
+		       instance->ao_idx);
+		return -ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+	} else {		//Add last value
+		value = *(instance->circ_buf.buf + pos);
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+	}
+
+	PINFO("WRAPAROUND LOADED %d values. idx=%d\n", local_count,
+	      instance->ao_idx);
+	return local_count;
+}
+
+/** @brief Copy data from software buffer to fifo (fast).
+* @note This is time critical function. Checking is done at begining and end only.
+* @note The is not reasonable way to check how many walues was in FIFO at begining. The count must be managed externaly.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied data.
+* @return On error/success: 0.	No datas were copied => no data in buffer.
+* @return On error: -ME_ERRNO_FIFO_BUFFER_OVERFLOW.
+*/
+int inline ao_write_data(me4600_ao_subdevice_t * instance, int count,
+			 int start_pos)
+{				/// @note This is time critical function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int max_count;
+	int i = 1;
+
+	if (count <= 0) {	//Wrong count!
+		return 0;
+	}
+
+	max_count = me_circ_buf_values(&instance->circ_buf) - start_pos;
+	if (max_count <= 0) {	//No data to copy!
+		return 0;
+	}
+
+	if (max_count < count) {
+		local_count = max_count;
+	}
+
+	while (i < local_count) {
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+		i++;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME4600_AO_STATUS_BIT_FF)) {	//FIFO is full before all datas were copied!
+		PERROR("FIFO was full before all datas were copied! idx=%d\n",
+		       instance->ao_idx);
+		return -ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+	} else {		//Add last value
+		value = *(instance->circ_buf.buf + pos);
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+	}
+
+	PINFO("FAST LOADED %d values. idx=%d\n", local_count, instance->ao_idx);
+	return local_count;
+}
+
+/** @brief Copy data from software buffer to fifo (slow).
+* @note This is slow function that copy all data from buffer to FIFO with full control.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied values.
+* @return On error/success: 0.	FIFO was full at begining.
+* @return On error: -ME_ERRNO_RING_BUFFER_UNDEFFLOW.
+*/
+int inline ao_write_data_pooling(me4600_ao_subdevice_t * instance, int count,
+				 int start_pos)
+{				/// @note This is slow function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int i;
+	int max_count;
+
+	if (count <= 0) {	//Wrong count!
+		PERROR("SLOW LOADED: Wrong count! idx=%d\n", instance->ao_idx);
+		return 0;
+	}
+
+	max_count = me_circ_buf_values(&instance->circ_buf) - start_pos;
+	if (max_count <= 0) {	//No data to copy!
+		PERROR("SLOW LOADED: No data to copy! idx=%d\n",
+		       instance->ao_idx);
+		return 0;
+	}
+
+	if (max_count < count) {
+		local_count = max_count;
+	}
+
+	for (i = 0; i < local_count; i++) {
+		status = inl(instance->status_reg);
+		if (!(status & ME4600_AO_STATUS_BIT_FF)) {	//FIFO is full!
+			return i;
+		}
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+	}
+
+	PINFO("SLOW LOADED %d values. idx=%d\n", local_count, instance->ao_idx);
+	return local_count;
+}
+
+/** @brief Copy data from user space to circular buffer.
+* @param instance The subdevice instance (pointer).
+* @param count Number of datas in user space.
+* @param user_values Buffer's pointer.
+*
+* @return On success: Number of copied values.
+* @return On error: -ME_ERRNO_INTERNAL.
+*/
+int inline ao_get_data_from_user(me4600_ao_subdevice_t * instance, int count,
+				 int *user_values)
+{
+	int i, err;
+	int empty_space;
+	int copied;
+	int value;
+
+	empty_space = me_circ_buf_space(&instance->circ_buf);
+	//We have only this space free.
+	copied = (count < empty_space) ? count : empty_space;
+	for (i = 0; i < copied; i++) {	//Copy from user to buffer
+		if ((err = get_user(value, (int *)(user_values + i)))) {
+			PERROR
+			    ("BUFFER LOADED: get_user(0x%p) return an error: %d. idx=%d\n",
+			     user_values + i, err, instance->ao_idx);
+			return -ME_ERRNO_INTERNAL;
+		}
+		/// @note The analog output in me4600 series has size of 16 bits.
+		*(instance->circ_buf.buf + instance->circ_buf.head) =
+		    (uint16_t) value;
+		instance->circ_buf.head++;
+		instance->circ_buf.head &= instance->circ_buf.mask;
+	}
+
+	PINFO("BUFFER LOADED %d values. idx=%d\n", copied, instance->ao_idx);
+	return copied;
+}
+
+/** @brief Checking actual hardware and logical state.
+* @param instance The subdevice instance (pointer).
+*/
+static void me4600_ao_work_control_task(
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+					       void *subdevice
+#else
+					       struct work_struct *work
+#endif
+    )
+{
+	me4600_ao_subdevice_t *instance;
+	unsigned long cpu_flags = 0;
+	uint32_t status;
+	uint32_t ctrl;
+	uint32_t synch;
+	int reschedule = 0;
+	int signaling = 0;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	instance = (me4600_ao_subdevice_t *) subdevice;
+#else
+	instance =
+	    container_of((void *)work, me4600_ao_subdevice_t, ao_control_task);
+#endif
+	PINFO("<%s: %ld> executed. idx=%d\n", __FUNCTION__, jiffies,
+	      instance->ao_idx);
+
+	status = inl(instance->status_reg);
+	PDEBUG_REG("status_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->status_reg - instance->reg_base, status);
+
+	switch (instance->status) {	// Checking actual mode.
+
+		// Not configured for work.
+	case ao_status_none:
+		break;
+
+		//This are stable modes. No need to do anything. (?)
+	case ao_status_single_configured:
+	case ao_status_stream_configured:
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		PERROR("Shouldn't be running!.\n");
+		break;
+
+	case ao_status_stream_end:
+		if (!instance->fifo) {
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+	case ao_status_single_end:
+		if (status & ME4600_AO_STATUS_BIT_FSM) {	// State machine is working but the status is set to end. Force stop.
+
+			// Wait for stop.
+			reschedule = 1;
+		}
+
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		// Stop all actions. No conditions! Block interrupts and trigger. Leave FIFO untouched!
+		ctrl = inl(instance->ctrl_reg);
+		ctrl |=
+		    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP | ME4600_AO_CTRL_BIT_STOP
+		    | ME4600_AO_CTRL_BIT_RESET_IRQ;
+		ctrl &=
+		    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+		      ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG);
+		ctrl &=
+		    ~(ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+		      ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH);
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		break;
+
+		// Single modes
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+
+		if (!(status & ME4600_AO_STATUS_BIT_FSM)) {	// State machine is not working.
+			if (((instance->fifo)
+			     && (!(status & ME4600_AO_STATUS_BIT_EF)))
+			    || (!(instance->fifo))) {	// Single is in end state.
+				PDEBUG("Single call has been complited.\n");
+
+				// Set correct value for single_read();
+				instance->single_value =
+				    instance->single_value_in_fifo;
+
+				// Set status as 'ao_status_single_end'
+				instance->status = ao_status_single_end;
+
+				// Signal the end.
+				signaling = 1;
+				// Wait for stop ISM.
+				reschedule = 1;
+
+				break;
+			}
+		}
+		// Check timeout.
+		if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+			PDEBUG("Timeout reached.\n");
+			// Stop all actions. No conditions! Block interrupts and trigger. Leave FIFO untouched!
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME4600_AO_CTRL_BIT_STOP |
+			    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP |
+			    ME4600_AO_CTRL_BIT_RESET_IRQ;
+			ctrl &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			/// Fix for timeout error.
+			ctrl &=
+			    ~(ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+			      ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH);
+			if (instance->fifo) {	//Disabling FIFO
+				ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+			}
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			spin_lock(instance->preload_reg_lock);
+			//Remove from synchronous start. Block triggering from this output.
+			synch = inl(instance->preload_reg);
+			synch &=
+			    ~((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) <<
+			      instance->ao_idx);
+			if (!(instance->fifo)) {	// No FIFO - set to single safe mode
+				synch |=
+				    ME4600_AO_SYNC_HOLD << instance->ao_idx;
+			}
+			outl(synch, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   synch);
+			spin_unlock(instance->preload_reg_lock);
+
+			if (!(instance->fifo)) {	// No FIFO
+				// Restore old settings.
+				PDEBUG("Write old value back to register.\n");
+				outl(instance->single_value,
+				     instance->single_reg);
+				PDEBUG_REG
+				    ("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->single_reg - instance->reg_base,
+				     instance->single_value);
+			}
+			// Set correct value for single_read();
+			instance->single_value_in_fifo = instance->single_value;
+
+			instance->status = ao_status_single_end;
+
+			// Signal the end.
+			signaling = 1;
+		}
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+		// Stream modes
+	case ao_status_stream_run_wait:
+		if (!instance->fifo) {
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (status & ME4600_AO_STATUS_BIT_FSM) {	// State machine is working. Waiting for start finish.
+			instance->status = ao_status_stream_run;
+
+			// Signal end of this step
+			signaling = 1;
+		} else {	// State machine is not working.
+			if (!(status & ME4600_AO_STATUS_BIT_EF)) {	// FIFO is empty. Procedure has started and finish already!
+				instance->status = ao_status_stream_end;
+
+				// Signal the end.
+				signaling = 1;
+				// Wait for stop.
+				reschedule = 1;
+				break;
+			}
+		}
+
+		// Check timeout.
+		if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+			PDEBUG("Timeout reached.\n");
+			// Stop all actions. No conditions! Block interrupts. Leave FIFO untouched!
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME4600_AO_CTRL_BIT_STOP |
+			    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP |
+			    ME4600_AO_CTRL_BIT_RESET_IRQ;
+			ctrl &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			spin_lock(instance->preload_reg_lock);
+			//Remove from synchronous start. Block triggering from this output.
+			synch = inl(instance->preload_reg);
+			synch &=
+			    ~((ME4600_AO_SYNC_HOLD | ME4600_AO_SYNC_EXT_TRIG) <<
+			      instance->ao_idx);
+			outl(synch, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   synch);
+			spin_unlock(instance->preload_reg_lock);
+
+			instance->status = ao_status_stream_end;
+
+			// Signal the end.
+			signaling = 1;
+		}
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+	case ao_status_stream_run:
+		if (!instance->fifo) {
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (!(status & ME4600_AO_STATUS_BIT_FSM)) {	// State machine is not working. This is an error.
+			// BROKEN PIPE!
+			if (!(status & ME4600_AO_STATUS_BIT_EF)) {	// FIFO is empty.
+				if (me_circ_buf_values(&instance->circ_buf)) {	// Software buffer is not empty.
+					if (instance->stop_data_count && (instance->stop_data_count <= instance->data_count)) {	//Finishing work. Requed data shown.
+						PDEBUG
+						    ("ISM stoped. No data in FIFO. Buffer is not empty.\n");
+						instance->status =
+						    ao_status_stream_end;
+					} else {
+						PERROR
+						    ("Output stream has been broken. ISM stoped. No data in FIFO. Buffer is not empty.\n");
+						instance->status =
+						    ao_status_stream_buffer_error;
+					}
+				} else {	// Software buffer is empty.
+					PDEBUG
+					    ("ISM stoped. No data in FIFO. Buffer is empty.\n");
+					instance->status = ao_status_stream_end;
+				}
+			} else {	// There are still datas in FIFO.
+				if (me_circ_buf_values(&instance->circ_buf)) {	// Software buffer is not empty.
+					PERROR
+					    ("Output stream has been broken. ISM stoped but some data in FIFO and buffer.\n");
+				} else {	// Software buffer is empty.
+					PERROR
+					    ("Output stream has been broken. ISM stoped but some data in FIFO. Buffer is empty.\n");
+				}
+				instance->status = ao_status_stream_fifo_error;
+
+			}
+
+			// Signal the failure.
+			signaling = 1;
+			break;
+		}
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+	case ao_status_stream_end_wait:
+		if (!instance->fifo) {
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (!(status & ME4600_AO_STATUS_BIT_FSM)) {	// State machine is not working. Waiting for stop finish.
+			instance->status = ao_status_stream_end;
+			signaling = 1;
+		}
+		// State machine is working.
+		reschedule = 1;
+		break;
+
+	default:
+		PERROR_CRITICAL("Status is in wrong state (%d)!\n",
+				instance->status);
+		instance->status = ao_status_stream_error;
+		// Signal the end.
+		signaling = 1;
+		break;
+
+	}
+
+	if (signaling) {	//Signal it.
+		wake_up_interruptible_all(&instance->wait_queue);
+	}
+
+	if (instance->ao_control_task_flag && reschedule) {	// Reschedule task
+		queue_delayed_work(instance->me4600_workqueue,
+				   &instance->ao_control_task, 1);
+	} else {
+		PINFO("<%s> Ending control task.\n", __FUNCTION__);
+	}
+
+}
+#else
+/// @note SPECIAL BUILD FOR BOSCH
+/// @author Guenter Gebhardt
+static int me4600_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t tmp;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock_irqsave(&instance->subdevice_lock, status);
+	spin_lock(instance->preload_reg_lock);
+	tmp = inl(instance->preload_reg);
+	tmp &= ~(0x10001 << instance->ao_idx);
+	outl(tmp, instance->preload_reg);
+	*instance->preload_flags &= ~(0x1 << instance->ao_idx);
+	spin_unlock(instance->preload_reg_lock);
+
+	tmp = inl(instance->ctrl_reg);
+	tmp |= ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+	outl(tmp, instance->ctrl_reg);
+
+	while (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) ;
+
+	outl(ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP,
+	     instance->ctrl_reg);
+
+	outl(0x8000, instance->single_reg);
+
+	instance->single_value = 0x8000;
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	if (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) {
+		PERROR("Subdevice is busy.\n");
+		err = ME_ERRNO_SUBDEVICE_BUSY;
+		goto ERROR;
+	}
+
+	if (channel == 0) {
+		if (single_config == 0) {
+			if (ref == ME_REF_AO_GROUND) {
+				if (trig_chan == ME_TRIG_CHAN_DEFAULT) {
+					if (trig_type == ME_TRIG_TYPE_SW) {
+						tmp = inl(instance->ctrl_reg);
+						tmp |=
+						    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+						outl(tmp, instance->ctrl_reg);
+						tmp =
+						    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+						outl(tmp, instance->ctrl_reg);
+
+						spin_lock(instance->
+							  preload_reg_lock);
+						tmp =
+						    inl(instance->preload_reg);
+						tmp &=
+						    ~(0x10001 << instance->
+						      ao_idx);
+						outl(tmp,
+						     instance->preload_reg);
+						*instance->preload_flags &=
+						    ~(0x1 << instance->ao_idx);
+						spin_unlock(instance->
+							    preload_reg_lock);
+					} else if (trig_type ==
+						   ME_TRIG_TYPE_EXT_DIGITAL) {
+						if (trig_edge ==
+						    ME_TRIG_EDGE_RISING) {
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+							    |
+							    ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else if (trig_edge ==
+							   ME_TRIG_EDGE_FALLING)
+						{
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+							    |
+							    ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else if (trig_edge ==
+							   ME_TRIG_EDGE_ANY) {
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+							    |
+							    ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else {
+							PERROR
+							    ("Invalid trigger edge.\n");
+							err =
+							    ME_ERRNO_INVALID_TRIG_EDGE;
+							goto ERROR;
+						}
+
+						spin_lock(instance->
+							  preload_reg_lock);
+
+						tmp =
+						    inl(instance->preload_reg);
+						tmp &=
+						    ~(0x10001 << instance->
+						      ao_idx);
+						tmp |= 0x1 << instance->ao_idx;
+						outl(tmp,
+						     instance->preload_reg);
+						*instance->preload_flags &=
+						    ~(0x1 << instance->ao_idx);
+						spin_unlock(instance->
+							    preload_reg_lock);
+					} else {
+						PERROR
+						    ("Invalid trigger type.\n");
+						err =
+						    ME_ERRNO_INVALID_TRIG_TYPE;
+						goto ERROR;
+					}
+				} else if (trig_chan ==
+					   ME_TRIG_CHAN_SYNCHRONOUS) {
+					if (trig_type == ME_TRIG_TYPE_SW) {
+						tmp = inl(instance->ctrl_reg);
+						tmp |=
+						    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+						outl(tmp, instance->ctrl_reg);
+						tmp =
+						    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+						outl(tmp, instance->ctrl_reg);
+
+						spin_lock(instance->
+							  preload_reg_lock);
+						tmp =
+						    inl(instance->preload_reg);
+						tmp &=
+						    ~(0x10001 << instance->
+						      ao_idx);
+						tmp |= 0x1 << instance->ao_idx;
+						outl(tmp,
+						     instance->preload_reg);
+						*instance->preload_flags |=
+						    0x1 << instance->ao_idx;
+						spin_unlock(instance->
+							    preload_reg_lock);
+					} else if (trig_type ==
+						   ME_TRIG_TYPE_EXT_DIGITAL) {
+						if (trig_edge ==
+						    ME_TRIG_EDGE_RISING) {
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else if (trig_edge ==
+							   ME_TRIG_EDGE_FALLING)
+						{
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else if (trig_edge ==
+							   ME_TRIG_EDGE_ANY) {
+							tmp =
+							    inl(instance->
+								ctrl_reg);
+							tmp |=
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+							tmp =
+							    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE
+							    |
+							    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+							outl(tmp,
+							     instance->
+							     ctrl_reg);
+						} else {
+							PERROR
+							    ("Invalid trigger edge.\n");
+							err =
+							    ME_ERRNO_INVALID_TRIG_EDGE;
+							goto ERROR;
+						}
+
+						spin_lock(instance->
+							  preload_reg_lock);
+
+						tmp =
+						    inl(instance->preload_reg);
+						tmp |=
+						    0x10001 << instance->ao_idx;
+						outl(tmp,
+						     instance->preload_reg);
+						*instance->preload_flags &=
+						    ~(0x1 << instance->ao_idx);
+						spin_unlock(instance->
+							    preload_reg_lock);
+					} else {
+						PERROR
+						    ("Invalid trigger type.\n");
+						err =
+						    ME_ERRNO_INVALID_TRIG_TYPE;
+						goto ERROR;
+					}
+				} else {
+					PERROR
+					    ("Invalid trigger channel specified.\n");
+					err = ME_ERRNO_INVALID_REF;
+					goto ERROR;
+				}
+			} else {
+				PERROR("Invalid analog reference specified.\n");
+				err = ME_ERRNO_INVALID_REF;
+				goto ERROR;
+			}
+		} else {
+			PERROR("Invalid single config specified.\n");
+			err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			goto ERROR;
+		}
+	} else {
+		PERROR("Invalid channel number specified.\n");
+		err = ME_ERRNO_INVALID_CHANNEL;
+		goto ERROR;
+	}
+
+      ERROR:
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	tmp = inl(instance->ctrl_reg);
+
+	if (tmp & 0x3) {
+		PERROR("Not in single mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+	} else {
+		*value = instance->single_value;
+	}
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long mask = 0;
+	unsigned long tmp;
+	unsigned long cpu_flags;
+	int i;
+	wait_queue_head_t queue;
+	unsigned long j;
+	unsigned long delay = 0;
+
+	PDEBUG("executed.\n");
+
+	init_waitqueue_head(&queue);
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	tmp = inl(instance->ctrl_reg);
+
+	if (tmp & 0x3) {
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		PERROR("Not in single mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+		goto ERROR;
+	}
+
+	if (tmp & ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG) {
+		outl(value, instance->single_reg);
+		instance->single_value = value;
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+		if (!(flags & ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+			j = jiffies;
+
+			while (inl(instance->status_reg) &
+			       ME4600_AO_STATUS_BIT_FSM) {
+				interruptible_sleep_on_timeout(&queue, 1);
+
+				if (signal_pending(current)) {
+					PERROR
+					    ("Wait on external trigger interrupted by signal.\n");
+					err = ME_ERRNO_SIGNAL;
+					goto ERROR;
+				}
+
+				if (delay && ((jiffies - j) > delay)) {
+					PERROR("Timeout reached.\n");
+					err = ME_ERRNO_TIMEOUT;
+					goto ERROR;
+				}
+			}
+		}
+	} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx))
+		   == (0x10001 << instance->ao_idx)) {
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+			outl(tmp, instance->ctrl_reg);
+			outl(value, instance->single_reg);
+			instance->single_value = value;
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (!(flags & ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+				j = jiffies;
+
+				while (inl(instance->status_reg) &
+				       ME4600_AO_STATUS_BIT_FSM) {
+					interruptible_sleep_on_timeout(&queue,
+								       1);
+
+					if (signal_pending(current)) {
+						PERROR
+						    ("Wait on external trigger interrupted by signal.\n");
+						err = ME_ERRNO_SIGNAL;
+						goto ERROR;
+					}
+
+					if (delay && ((jiffies - j) > delay)) {
+						PERROR("Timeout reached.\n");
+						err = ME_ERRNO_TIMEOUT;
+						goto ERROR;
+					}
+				}
+			}
+		} else {
+			outl(value, instance->single_reg);
+			instance->single_value = value;
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+	} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx))
+		   == (0x1 << instance->ao_idx)) {
+		outl(value, instance->single_reg);
+		instance->single_value = value;
+
+		PDEBUG("Synchronous SW, flags = 0x%X.\n", flags);
+
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {
+			PDEBUG("Trigger synchronous SW.\n");
+			spin_lock(instance->preload_reg_lock);
+			tmp = inl(instance->preload_reg);
+
+			for (i = 0; i < ME4600_AO_MAX_SUBDEVICES; i++) {
+				if ((*instance->preload_flags & (0x1 << i))) {
+					if ((tmp & (0x10001 << i)) ==
+					    (0x1 << i)) {
+						mask |= 0x1 << i;
+					}
+				}
+			}
+
+			tmp &= ~(mask);
+
+			outl(tmp, instance->preload_reg);
+			spin_unlock(instance->preload_reg_lock);
+		}
+
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	} else {
+		outl(value, instance->single_reg);
+		instance->single_value = value;
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	}
+
+      ERROR:
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long ctrl;
+	unsigned long tmp;
+	unsigned long cpu_flags;
+	uint64_t conv_ticks;
+	unsigned int conv_start_ticks_low = trigger->iConvStartTicksLow;
+	unsigned int conv_start_ticks_high = trigger->iConvStartTicksHigh;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	conv_ticks =
+	    (uint64_t) conv_start_ticks_low +
+	    ((uint64_t) conv_start_ticks_high << 32);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	if ((inl(instance->status_reg)) & ME4600_AO_STATUS_BIT_FSM) {
+		PERROR("Subdevice is busy.\n");
+		err = ME_ERRNO_SUBDEVICE_BUSY;
+		goto ERROR;
+	}
+
+	ctrl = inl(instance->ctrl_reg);
+	ctrl |= ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+	outl(ctrl, instance->ctrl_reg);
+	ctrl = ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+	outl(ctrl, instance->ctrl_reg);
+
+	if (count != 1) {
+		PERROR("Invalid stream configuration list count specified.\n");
+		err = ME_ERRNO_INVALID_CONFIG_LIST_COUNT;
+		goto ERROR;
+	}
+
+	if (config_list[0].iChannel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		err = ME_ERRNO_INVALID_CHANNEL;
+		goto ERROR;
+	}
+
+	if (config_list[0].iStreamConfig != 0) {
+		PERROR("Invalid stream config specified.\n");
+		err = ME_ERRNO_INVALID_STREAM_CONFIG;
+		goto ERROR;
+	}
+
+	if (config_list[0].iRef != ME_REF_AO_GROUND) {
+		PERROR("Invalid analog reference.\n");
+		err = ME_ERRNO_INVALID_REF;
+		goto ERROR;
+	}
+
+	if ((trigger->iAcqStartTicksLow != 0)
+	    || (trigger->iAcqStartTicksHigh != 0)) {
+		PERROR
+		    ("Invalid acquisition start trigger argument specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_ARG;
+		goto ERROR;
+	}
+
+	switch (trigger->iAcqStartTrigType) {
+
+	case ME_TRIG_TYPE_SW:
+		break;
+
+	case ME_TRIG_TYPE_EXT_DIGITAL:
+		ctrl |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		switch (trigger->iAcqStartTrigEdge) {
+
+		case ME_TRIG_EDGE_RISING:
+			break;
+
+		case ME_TRIG_EDGE_FALLING:
+			ctrl |= ME4600_AO_CTRL_BIT_EX_TRIG_EDGE;
+
+			break;
+
+		case ME_TRIG_EDGE_ANY:
+			ctrl |=
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE |
+			    ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+
+			break;
+
+		default:
+			PERROR
+			    ("Invalid acquisition start trigger edge specified.\n");
+
+			err = ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+
+			goto ERROR;
+
+			break;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid acquisition start trigger type specified.\n");
+
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_TYPE;
+
+		goto ERROR;
+
+		break;
+	}
+
+	switch (trigger->iScanStartTrigType) {
+
+	case ME_TRIG_TYPE_FOLLOW:
+		break;
+
+	default:
+		PERROR("Invalid scan start trigger type specified.\n");
+
+		err = ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+
+		goto ERROR;
+
+		break;
+	}
+
+	switch (trigger->iConvStartTrigType) {
+
+	case ME_TRIG_TYPE_TIMER:
+		if ((conv_ticks < ME4600_AO_MIN_CHAN_TICKS)
+		    || (conv_ticks > ME4600_AO_MAX_CHAN_TICKS)) {
+			PERROR
+			    ("Invalid conv start trigger argument specified.\n");
+			err = ME_ERRNO_INVALID_CONV_START_ARG;
+			goto ERROR;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid conv start trigger type specified.\n");
+
+		err = ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+
+		goto ERROR;
+
+		break;
+	}
+
+	/* Preset to hardware wraparound mode */
+	instance->flags &= ~(ME4600_AO_FLAGS_SW_WRAP_MODE_MASK);
+
+	switch (trigger->iScanStopTrigType) {
+
+	case ME_TRIG_TYPE_NONE:
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			/* Set flags to indicate usage of software mode. */
+			instance->flags |= ME4600_AO_FLAGS_SW_WRAP_MODE_INF;
+			instance->wrap_count = 0;
+			instance->wrap_remaining = 0;
+		}
+
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iScanStopCount <= 0) {
+				PERROR("Invalid scan stop count specified.\n");
+				err = ME_ERRNO_INVALID_SCAN_STOP_ARG;
+				goto ERROR;
+			}
+
+			/* Set flags to indicate usage of software mode. */
+			instance->flags |= ME4600_AO_FLAGS_SW_WRAP_MODE_FIN;
+			instance->wrap_count = trigger->iScanStopCount;
+			instance->wrap_remaining = trigger->iScanStopCount;
+		} else {
+			PERROR("Invalid scan stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid scan stop trigger type specified.\n");
+
+		err = ME_ERRNO_INVALID_SCAN_STOP_TRIG_TYPE;
+
+		goto ERROR;
+
+		break;
+	}
+
+	switch (trigger->iAcqStopTrigType) {
+
+	case ME_TRIG_TYPE_NONE:
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iAcqStopCount <= 0) {
+				PERROR("Invalid acq stop count specified.\n");
+				err = ME_ERRNO_INVALID_ACQ_STOP_ARG;
+				goto ERROR;
+			}
+
+			/* Set flags to indicate usage of software mode. */
+			instance->flags |= ME4600_AO_FLAGS_SW_WRAP_MODE_FIN;
+			instance->wrap_count = trigger->iAcqStopCount;
+			instance->wrap_remaining = trigger->iAcqStopCount;
+		} else {
+			PERROR("Invalid acp stop trigger type specified.\n");
+			err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+			goto ERROR;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid acq stop trigger type specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		goto ERROR;
+		break;
+	}
+
+	switch (trigger->iAcqStartTrigChan) {
+
+	case ME_TRIG_CHAN_DEFAULT:
+		spin_lock(instance->preload_reg_lock);
+		tmp = inl(instance->preload_reg);
+		tmp &= ~(0x10001 << instance->ao_idx);
+		outl(tmp, instance->preload_reg);
+		spin_unlock(instance->preload_reg_lock);
+
+		break;
+
+	case ME_TRIG_CHAN_SYNCHRONOUS:
+		if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_SW) {
+			spin_lock(instance->preload_reg_lock);
+			tmp = inl(instance->preload_reg);
+			tmp &= ~(0x10001 << instance->ao_idx);
+			outl(tmp, instance->preload_reg);
+			tmp |= 0x1 << instance->ao_idx;
+			outl(tmp, instance->preload_reg);
+			spin_unlock(instance->preload_reg_lock);
+		} else {
+			ctrl &= ~(ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			spin_lock(instance->preload_reg_lock);
+			tmp = inl(instance->preload_reg);
+			tmp &= ~(0x10001 << instance->ao_idx);
+			outl(tmp, instance->preload_reg);
+			tmp |= 0x10000 << instance->ao_idx;
+			outl(tmp, instance->preload_reg);
+			spin_unlock(instance->preload_reg_lock);
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid acq start trigger channel specified.\n");
+		err = ME_ERRNO_INVALID_ACQ_START_TRIG_CHAN;
+		goto ERROR;
+
+		break;
+	}
+
+	outl(conv_ticks - 2, instance->timer_reg);
+
+	if (flags & ME_IO_STREAM_CONFIG_BIT_PATTERN) {
+		if (instance->ao_idx == 3) {
+			ctrl |= ME4600_AO_CTRL_BIT_ENABLE_DO;
+		} else {
+			err = ME_ERRNO_INVALID_FLAGS;
+			goto ERROR;
+		}
+	} else {
+		if (instance->ao_idx == 3) {
+			ctrl &= ~ME4600_AO_CTRL_BIT_ENABLE_DO;
+		}
+	}
+
+	/* Set hardware mode. */
+	if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+		ctrl |= ME4600_AO_CTRL_BIT_MODE_0;
+	} else {
+		ctrl |= ME4600_AO_CTRL_BIT_MODE_1;
+	}
+
+	PDEBUG("Preload word = 0x%X.\n", inl(instance->preload_reg));
+
+	PDEBUG("Ctrl word = 0x%lX.\n", ctrl);
+	outl(ctrl, instance->ctrl_reg);	// Write the control word
+
+      ERROR:
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	long j;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	*count = 0;
+
+	ME_SUBDEVICE_ENTER;
+
+	if (t) {
+		j = jiffies;
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((me_circ_buf_space
+						   (&instance->circ_buf))
+						  || !(inl(instance->status_reg)
+						       &
+						       ME4600_AO_STATUS_BIT_FSM)),
+						 t);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PERROR("AO subdevice is not running.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+		} else if (signal_pending(current)) {
+			PERROR("Wait on values interrupted from signal.\n");
+			err = ME_ERRNO_SIGNAL;
+		} else if ((jiffies - j) >= t) {
+			PERROR("Wait on values timed out.\n");
+			err = ME_ERRNO_TIMEOUT;
+		} else {
+			*count = me_circ_buf_space(&instance->circ_buf);
+		}
+	} else {
+		wait_event_interruptible(instance->wait_queue,
+					 ((me_circ_buf_space
+					   (&instance->circ_buf))
+					  || !(inl(instance->status_reg) &
+					       ME4600_AO_STATUS_BIT_FSM)));
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PERROR("AO subdevice is not running.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+		} else if (signal_pending(current)) {
+			PERROR("Wait on values interrupted from signal.\n");
+			err = ME_ERRNO_SIGNAL;
+		} else {
+			*count = me_circ_buf_space(&instance->circ_buf);
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static void stop_immediately(me4600_ao_subdevice_t * instance)
+{
+	unsigned long cpu_flags;
+	uint32_t tmp;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	tmp = inl(instance->ctrl_reg);
+	tmp |= ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+	outl(tmp, instance->ctrl_reg);
+
+	while (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) ;
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+}
+
+static int me4600_ao_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags = 0;
+	unsigned long ref;
+	unsigned long tmp;
+	unsigned long delay = 0;
+	wait_queue_head_t queue;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	init_waitqueue_head(&queue);
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	ME_SUBDEVICE_ENTER
+	    spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	tmp = inl(instance->ctrl_reg);
+
+	switch (tmp & (ME4600_AO_CTRL_MASK_MODE)) {
+
+	case 0:		// Single mode
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		PERROR("Subdevice is configured in single mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+		goto ERROR;
+
+	case 1:		// Wraparound mode
+		if (tmp & ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG) {	// Normal wraparound with external trigger
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+			outl(tmp, instance->ctrl_reg);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (start_mode == ME_START_MODE_BLOCKING) {
+				init_waitqueue_head(&queue);
+
+				if (delay) {
+					ref = jiffies;
+
+					while (!
+					       (inl(instance->status_reg) &
+						ME4600_AO_STATUS_BIT_FSM)) {
+						interruptible_sleep_on_timeout
+						    (&queue, 1);
+
+						if (signal_pending(current)) {
+							PERROR
+							    ("Wait on start of state machine interrupted.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_SIGNAL;
+							goto ERROR;
+						}
+
+						if (((jiffies - ref) >= delay)) {
+							PERROR
+							    ("Timeout reached.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_TIMEOUT;
+							goto ERROR;
+						}
+					}
+				} else {
+					while (!
+					       (inl(instance->status_reg) &
+						ME4600_AO_STATUS_BIT_FSM)) {
+						interruptible_sleep_on_timeout
+						    (&queue, 1);
+
+						if (signal_pending(current)) {
+							PERROR
+							    ("Wait on start of state machine interrupted.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_SIGNAL;
+							goto ERROR;
+						}
+					}
+				}
+			} else if (start_mode == ME_START_MODE_NONBLOCKING) {
+			} else {
+				PERROR("Invalid start mode specified.\n");
+				err = ME_ERRNO_INVALID_START_MODE;
+				goto ERROR;
+			}
+		} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx)) == (0x10000 << instance->ao_idx)) {	// Synchronous with external trigger
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			if (flags & ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+				tmp |= ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG;
+				tmp &=
+				    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+				      ME4600_AO_CTRL_BIT_STOP |
+				      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+				outl(tmp, instance->ctrl_reg);
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+
+				if (start_mode == ME_START_MODE_BLOCKING) {
+					init_waitqueue_head(&queue);
+
+					if (delay) {
+						ref = jiffies;
+
+						while (!
+						       (inl
+							(instance->
+							 status_reg) &
+							ME4600_AO_STATUS_BIT_FSM))
+						{
+							interruptible_sleep_on_timeout
+							    (&queue, 1);
+
+							if (signal_pending
+							    (current)) {
+								PERROR
+								    ("Wait on start of state machine interrupted.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_SIGNAL;
+								goto ERROR;
+							}
+
+							if (((jiffies - ref) >=
+							     delay)) {
+								PERROR
+								    ("Timeout reached.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_TIMEOUT;
+								goto ERROR;
+							}
+						}
+					} else {
+						while (!
+						       (inl
+							(instance->
+							 status_reg) &
+							ME4600_AO_STATUS_BIT_FSM))
+						{
+							interruptible_sleep_on_timeout
+							    (&queue, 1);
+
+							if (signal_pending
+							    (current)) {
+								PERROR
+								    ("Wait on start of state machine interrupted.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_SIGNAL;
+								goto ERROR;
+							}
+						}
+					}
+				} else if (start_mode ==
+					   ME_START_MODE_NONBLOCKING) {
+				} else {
+					PERROR
+					    ("Invalid start mode specified.\n");
+					err = ME_ERRNO_INVALID_START_MODE;
+					goto ERROR;
+				}
+			} else {
+				tmp &=
+				    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+				      ME4600_AO_CTRL_BIT_STOP |
+				      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+				outl(tmp, instance->ctrl_reg);
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+			}
+		} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx)) == (0x1 << instance->ao_idx)) {	// Synchronous wraparound with sw trigger
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+			outl(tmp, instance->ctrl_reg);
+
+			if (flags & ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+				outl(0x8000, instance->single_reg);
+				instance->single_value = 0x8000;
+			}
+
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		} else {	// Software start
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+			outl(tmp, instance->ctrl_reg);
+
+			outl(0x8000, instance->single_reg);
+			instance->single_value = 0x8000;
+
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+
+		break;
+
+	case 2:		// Continuous mode
+		if (tmp & ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG) {	// Externally triggered
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(tmp, instance->ctrl_reg);
+			instance->wrap_remaining = instance->wrap_count;
+			instance->circ_buf.tail = 0;
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (start_mode == ME_START_MODE_BLOCKING) {
+				init_waitqueue_head(&queue);
+
+				if (delay) {
+					ref = jiffies;
+
+					while (!
+					       (inl(instance->status_reg) &
+						ME4600_AO_STATUS_BIT_FSM)) {
+						interruptible_sleep_on_timeout
+						    (&queue, 1);
+
+						if (signal_pending(current)) {
+							PERROR
+							    ("Wait on start of state machine interrupted.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_SIGNAL;
+							goto ERROR;
+						}
+
+						if (((jiffies - ref) >= delay)) {
+							PERROR
+							    ("Timeout reached.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_TIMEOUT;
+							goto ERROR;
+						}
+					}
+				} else {
+					while (!
+					       (inl(instance->status_reg) &
+						ME4600_AO_STATUS_BIT_FSM)) {
+						interruptible_sleep_on_timeout
+						    (&queue, 1);
+
+						if (signal_pending(current)) {
+							PERROR
+							    ("Wait on start of state machine interrupted.\n");
+							stop_immediately
+							    (instance);
+							err = ME_ERRNO_SIGNAL;
+							goto ERROR;
+						}
+					}
+				}
+			} else if (start_mode == ME_START_MODE_NONBLOCKING) {
+				/* Do nothing */
+			} else {
+				PERROR("Invalid start mode specified.\n");
+				stop_immediately(instance);
+				err = ME_ERRNO_INVALID_START_MODE;
+				goto ERROR;
+			}
+		} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx)) == (0x10000 << instance->ao_idx)) {	// Synchronous with external trigger
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			if (flags & ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+				tmp |=
+				    ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG |
+				    ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+				tmp &=
+				    ~(ME4600_AO_CTRL_BIT_STOP |
+				      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+				outl(tmp, instance->ctrl_reg);
+				instance->wrap_remaining = instance->wrap_count;
+				instance->circ_buf.tail = 0;
+
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+
+				if (start_mode == ME_START_MODE_BLOCKING) {
+					init_waitqueue_head(&queue);
+
+					if (delay) {
+						ref = jiffies;
+
+						while (!
+						       (inl
+							(instance->
+							 status_reg) &
+							ME4600_AO_STATUS_BIT_FSM))
+						{
+							interruptible_sleep_on_timeout
+							    (&queue, 1);
+
+							if (signal_pending
+							    (current)) {
+								PERROR
+								    ("Wait on start of state machine interrupted.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_SIGNAL;
+								goto ERROR;
+							}
+
+							if (((jiffies - ref) >=
+							     delay)) {
+								PERROR
+								    ("Timeout reached.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_TIMEOUT;
+								goto ERROR;
+							}
+						}
+					} else {
+						while (!
+						       (inl
+							(instance->
+							 status_reg) &
+							ME4600_AO_STATUS_BIT_FSM))
+						{
+							interruptible_sleep_on_timeout
+							    (&queue, 1);
+
+							if (signal_pending
+							    (current)) {
+								PERROR
+								    ("Wait on start of state machine interrupted.\n");
+								stop_immediately
+								    (instance);
+								err =
+								    ME_ERRNO_SIGNAL;
+								goto ERROR;
+							}
+						}
+					}
+				} else if (start_mode ==
+					   ME_START_MODE_NONBLOCKING) {
+				} else {
+					PERROR
+					    ("Invalid start mode specified.\n");
+					stop_immediately(instance);
+					err = ME_ERRNO_INVALID_START_MODE;
+					goto ERROR;
+				}
+			} else {
+				tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+				tmp &=
+				    ~(ME4600_AO_CTRL_BIT_STOP |
+				      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+				outl(tmp, instance->ctrl_reg);
+				instance->wrap_remaining = instance->wrap_count;
+				instance->circ_buf.tail = 0;
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+			}
+		} else if ((inl(instance->preload_reg) & (0x10001 << instance->ao_idx)) == (0x1 << instance->ao_idx)) {	// Synchronous wraparound with sw trigger
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			instance->wrap_remaining = instance->wrap_count;
+			instance->circ_buf.tail = 0;
+			PDEBUG("CTRL Reg = 0x%X.\n", inl(instance->ctrl_reg));
+			outl(tmp, instance->ctrl_reg);
+
+			if (flags & ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+				outl(0x8000, instance->single_reg);
+				instance->single_value = 0x8000;
+			}
+
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		} else {	// Software start
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR("Conversion is already running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_STOP |
+			      ME4600_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(tmp, instance->ctrl_reg);
+			outl(0x8000, instance->single_reg);
+			instance->single_value = 0x8000;
+			instance->wrap_remaining = instance->wrap_count;
+			instance->circ_buf.tail = 0;
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+
+		break;
+
+	default:
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		PERROR("Invalid mode configured.\n");
+		err = ME_ERRNO_INTERNAL;
+		goto ERROR;
+	}
+
+      ERROR:
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	wait_queue_head_t queue;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	init_waitqueue_head(&queue);
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (wait == ME_WAIT_NONE) {
+		*status =
+		    (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) ?
+		    ME_STATUS_BUSY : ME_STATUS_IDLE;
+		*values = me_circ_buf_space(&instance->circ_buf);
+	} else if (wait == ME_WAIT_IDLE) {
+		while (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) {
+			interruptible_sleep_on_timeout(&queue, 1);
+
+			if (instance->flags & ME4600_AO_FLAGS_BROKEN_PIPE) {
+				PERROR("Output stream was interrupted.\n");
+				*status = ME_STATUS_ERROR;
+				err = ME_ERRNO_SUCCESS;
+				goto ERROR;
+			}
+
+			if (signal_pending(current)) {
+				PERROR
+				    ("Wait on state machine interrupted by signal.\n");
+				*status = ME_STATUS_INVALID;
+				err = ME_ERRNO_SIGNAL;
+				goto ERROR;
+			}
+		}
+
+		*status = ME_STATUS_IDLE;
+
+		*values = me_circ_buf_space(&instance->circ_buf);
+	} else {
+		PERROR("Invalid wait argument specified.\n");
+		*status = ME_STATUS_INVALID;
+		err = ME_ERRNO_INVALID_WAIT;
+		goto ERROR;
+	}
+
+      ERROR:
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags)
+{
+	int err = ME_ERRNO_SUCCESS;
+	me4600_ao_subdevice_t *instance;
+	unsigned long cpu_flags;
+	unsigned long tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (stop_mode == ME_STOP_MODE_IMMEDIATE) {
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		tmp = inl(instance->ctrl_reg);
+		tmp |=
+		    ME4600_AO_CTRL_BIT_STOP | ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+		outl(tmp, instance->ctrl_reg);
+
+		while (inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM) ;
+
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	} else if (stop_mode == ME_STOP_MODE_LAST_VALUE) {
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		tmp = inl(instance->ctrl_reg);
+		tmp |= ME4600_AO_CTRL_BIT_STOP;
+		outl(tmp, instance->ctrl_reg);
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	} else {
+		PERROR("Invalid stop mode specified.\n");
+		err = ME_ERRNO_INVALID_STOP_MODE;
+		goto ERROR;
+	}
+
+      ERROR:
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ao_io_stream_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int write_mode,
+				     int *values, int *count, int flags)
+{
+	int err = ME_ERRNO_SUCCESS;
+	me4600_ao_subdevice_t *instance;
+	unsigned long tmp;
+	int i;
+	int value;
+	int cnt = *count;
+	int c;
+	int k;
+	int ret = 0;
+	unsigned long cpu_flags = 0;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	if (!instance->fifo) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (*count <= 0) {
+		PERROR("Invalid count of values specified.\n");
+		err = ME_ERRNO_INVALID_VALUE_COUNT;
+		goto ERROR;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	tmp = inl(instance->ctrl_reg);
+
+	switch (tmp & 0x3) {
+
+	case 1:		// Wraparound mode
+		if (instance->bosch_fw) {	// Bosch firmware
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (cnt != 7) {
+				PERROR
+				    ("Invalid count of values specified. 7 expected.\n");
+				err = ME_ERRNO_INVALID_VALUE_COUNT;
+				goto ERROR;
+			}
+
+			for (i = 0; i < 7; i++) {
+				if (get_user(value, values)) {
+					PERROR
+					    ("Can't copy value from user space.\n");
+					err = ME_ERRNO_INTERNAL;
+					goto ERROR;
+				}
+
+				if (i == 0) {
+					/* Maximum voltage */
+					value <<= 16;
+					value |=
+					    inl(instance->reg_base +
+						0xD4) & 0xFFFF;
+					outl(value, instance->reg_base + 0xD4);
+				} else if (i == 1) {
+					/* Minimum voltage */
+					value &= 0xFFFF;
+					value |=
+					    inl(instance->reg_base +
+						0xD4) & 0xFFFF0000;
+					outl(value, instance->reg_base + 0xD4);
+				} else if (i == 2) {
+					/* Delta up */
+					value <<= 16;
+					value |=
+					    inl(instance->reg_base +
+						0xD8) & 0xFFFF;
+					outl(value, instance->reg_base + 0xD8);
+				} else if (i == 3) {
+					/* Delta down */
+					value &= 0xFFFF;
+					value |=
+					    inl(instance->reg_base +
+						0xD8) & 0xFFFF0000;
+					outl(value, instance->reg_base + 0xD8);
+				} else if (i == 4) {
+					/* Start value */
+					outl(value, instance->reg_base + 0xDC);
+				} else if (i == 5) {
+					/* Invert */
+					if (value) {
+						value = inl(instance->ctrl_reg);
+						value |= 0x100;
+						outl(value, instance->ctrl_reg);
+					} else {
+						value = inl(instance->ctrl_reg);
+						value &= ~0x100;
+						outl(value, instance->ctrl_reg);
+					}
+				} else if (i == 6) {
+					/* Timer for positive ramp */
+					outl(value, instance->reg_base + 0xE0);
+				}
+
+				values++;
+			}
+		} else {	// Normal firmware
+			PDEBUG("Write for wraparound mode.\n");
+
+			if (inl(instance->status_reg) &
+			    ME4600_AO_STATUS_BIT_FSM) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR
+				    ("There is already a conversion running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp |= ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+			outl(tmp, instance->ctrl_reg);
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+
+			if ((*count > ME4600_AO_FIFO_COUNT) ||
+			    ((instance->
+			      flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK) ==
+			     ME4600_AO_FLAGS_SW_WRAP_MODE_FIN)) {
+				tmp &=
+				    ~(ME4600_AO_CTRL_BIT_MODE_0 |
+				      ME4600_AO_CTRL_BIT_MODE_1);
+				tmp |= ME4600_AO_CTRL_BIT_MODE_1;
+			}
+
+			outl(tmp, instance->ctrl_reg);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if ((*count <= ME4600_AO_FIFO_COUNT) &&
+			    ((instance->
+			      flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK) ==
+			     ME4600_AO_FLAGS_SW_WRAP_MODE_INF)) {
+				for (i = 0; i < *count; i++) {
+					if (get_user(value, values + i)) {
+						PERROR
+						    ("Cannot copy value from user space.\n");
+						err = ME_ERRNO_INTERNAL;
+						goto ERROR;
+					}
+
+					if (instance->ao_idx & 0x1)
+						value <<= 16;
+
+					outl(value, instance->fifo_reg);
+				}
+			} else if ((*count <= ME4600_AO_CIRC_BUF_COUNT) &&
+				   ((instance->
+				     flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK)
+				    == ME4600_AO_FLAGS_SW_WRAP_MODE_INF)) {
+				for (i = 0; i < *count; i++) {
+					if (get_user(value, values + i)) {
+						PERROR
+						    ("Cannot copy value from user space.\n");
+						err = ME_ERRNO_INTERNAL;
+						goto ERROR;
+					}
+
+					instance->circ_buf.buf[i] = value;	/* Used to hold the values. */
+				}
+
+				instance->circ_buf.tail = 0;	/* Used as the current read position. */
+				instance->circ_buf.head = *count;	/* Used as the buffer size. */
+
+				/* Preload the FIFO. */
+
+				for (i = 0; i < ME4600_AO_FIFO_COUNT;
+				     i++, instance->circ_buf.tail++) {
+					if (instance->circ_buf.tail >=
+					    instance->circ_buf.head)
+						instance->circ_buf.tail = 0;
+
+					if (instance->ao_idx & 0x1)
+						outl(instance->circ_buf.
+						     buf[instance->circ_buf.
+							 tail] << 16,
+						     instance->fifo_reg);
+					else
+						outl(instance->circ_buf.
+						     buf[instance->circ_buf.
+							 tail],
+						     instance->fifo_reg);
+				}
+			} else if ((*count <= ME4600_AO_CIRC_BUF_COUNT) &&
+				   ((instance->
+				     flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK)
+				    == ME4600_AO_FLAGS_SW_WRAP_MODE_FIN)) {
+				unsigned int preload_count;
+
+				for (i = 0; i < *count; i++) {
+					if (get_user(value, values + i)) {
+						PERROR
+						    ("Cannot copy value from user space.\n");
+						err = ME_ERRNO_INTERNAL;
+						goto ERROR;
+					}
+
+					instance->circ_buf.buf[i] = value;	/* Used to hold the values. */
+				}
+
+				instance->circ_buf.tail = 0;	/* Used as the current read position. */
+				instance->circ_buf.head = *count;	/* Used as the buffer size. */
+
+				/* Try to preload the whole FIFO. */
+				preload_count = ME4600_AO_FIFO_COUNT;
+
+				if (preload_count > instance->wrap_count)
+					preload_count = instance->wrap_count;
+
+				/* Preload the FIFO. */
+				for (i = 0; i < preload_count;
+				     i++, instance->circ_buf.tail++) {
+					if (instance->circ_buf.tail >=
+					    instance->circ_buf.head)
+						instance->circ_buf.tail = 0;
+
+					if (instance->ao_idx & 0x1)
+						outl(instance->circ_buf.
+						     buf[instance->circ_buf.
+							 tail] << 16,
+						     instance->fifo_reg);
+					else
+						outl(instance->circ_buf.
+						     buf[instance->circ_buf.
+							 tail],
+						     instance->fifo_reg);
+				}
+
+				instance->wrap_remaining =
+				    instance->wrap_count - preload_count;
+			} else {
+				PERROR("To many values written.\n");
+				err = ME_ERRNO_INVALID_VALUE_COUNT;
+				goto ERROR;
+			}
+		}
+
+		break;
+
+	case 2:		// Continuous mode
+		/* Check if in SW wrapround mode */
+		if (instance->flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK) {
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			PERROR("Subdevice is configured SW wrapround mode.\n");
+			err = ME_ERRNO_PREVIOUS_CONFIG;
+			goto ERROR;
+		}
+
+		switch (write_mode) {
+
+		case ME_WRITE_MODE_BLOCKING:
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			PDEBUG("Write for blocking continuous mode.\n");
+
+			while (cnt > 0) {
+				wait_event_interruptible(instance->wait_queue,
+							 (c =
+							  me_circ_buf_space_to_end
+							  (&instance->
+							   circ_buf)));
+
+				if (instance->
+				    flags & ME4600_AO_FLAGS_BROKEN_PIPE) {
+					PERROR
+					    ("Broken pipe in blocking write.\n");
+					err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+					goto ERROR;
+				} else if (signal_pending(current)) {
+					PERROR
+					    ("Wait for free buffer interrupted from signal.\n");
+					err = ME_ERRNO_SIGNAL;
+					goto ERROR;
+				}
+
+				PDEBUG("Space to end = %d.\n", c);
+
+				/* Only able to write size of free buffer or size of count */
+
+				if (cnt < c)
+					c = cnt;
+				k = sizeof(int) * c;
+				k -= copy_from_user(instance->circ_buf.buf +
+						    instance->circ_buf.head,
+						    values, k);
+				c = k / sizeof(int);
+
+				PDEBUG("Copy %d values from user space.\n", c);
+
+				if (!c) {
+					PERROR
+					    ("Cannot copy values from user space.\n");
+					err = ME_ERRNO_INTERNAL;
+					goto ERROR;
+				}
+
+				instance->circ_buf.head =
+				    (instance->circ_buf.head +
+				     c) & (instance->circ_buf.mask);
+
+				values += c;
+				cnt -= c;
+				ret += c;
+
+				/* Values are now available so enable interrupts */
+				spin_lock_irqsave(&instance->subdevice_lock,
+						  cpu_flags);
+
+				if (me_circ_buf_space(&instance->circ_buf)) {
+					tmp = inl(instance->ctrl_reg);
+					tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+					outl(tmp, instance->ctrl_reg);
+				}
+
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+			}
+
+			*count = ret;
+
+			break;
+
+		case ME_WRITE_MODE_NONBLOCKING:
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			PDEBUG("Write for non blocking continuous mode.\n");
+
+			while (cnt > 0) {
+				if (instance->
+				    flags & ME4600_AO_FLAGS_BROKEN_PIPE) {
+					PERROR
+					    ("ME4600:Broken pipe in nonblocking write.\n");
+					err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+					goto ERROR;
+				}
+
+				c = me_circ_buf_space_to_end(&instance->
+							     circ_buf);
+
+				if (!c) {
+					PDEBUG
+					    ("Returning from nonblocking write.\n");
+					break;
+				}
+
+				PDEBUG("Space to end = %d.\n", c);
+
+				/* Only able to write size of free buffer or size of count */
+
+				if (cnt < c)
+					c = cnt;
+				k = sizeof(int) * c;
+				k -= copy_from_user(instance->circ_buf.buf +
+						    instance->circ_buf.head,
+						    values, k);
+				c = k / sizeof(int);
+
+				PDEBUG("Copy %d values from user space.\n", c);
+
+				if (!c) {
+					PERROR
+					    ("Cannot copy values from user space.\n");
+					err = ME_ERRNO_INTERNAL;
+					goto ERROR;
+				}
+
+				instance->circ_buf.head =
+				    (instance->circ_buf.head +
+				     c) & (instance->circ_buf.mask);
+
+				values += c;
+				cnt -= c;
+				ret += c;
+
+				/* Values are now available so enable interrupts */
+				spin_lock_irqsave(&instance->subdevice_lock,
+						  cpu_flags);
+
+				if (me_circ_buf_space(&instance->circ_buf)) {
+					tmp = inl(instance->ctrl_reg);
+					tmp |= ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+					outl(tmp, instance->ctrl_reg);
+				}
+
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+			}
+
+			*count = ret;
+
+			break;
+
+		case ME_WRITE_MODE_PRELOAD:
+			PDEBUG("Write for preload continuous mode.\n");
+
+			if ((inl(instance->status_reg) &
+			     ME4600_AO_STATUS_BIT_FSM)) {
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				PERROR
+				    ("Can't Preload DAC FIFO while conversion is running.\n");
+				err = ME_ERRNO_SUBDEVICE_BUSY;
+				goto ERROR;
+			}
+
+			tmp = inl(instance->ctrl_reg);
+
+			tmp |=
+			    ME4600_AO_CTRL_BIT_STOP |
+			    ME4600_AO_CTRL_BIT_IMMEDIATE_STOP;
+			outl(tmp, instance->ctrl_reg);
+			tmp &=
+			    ~(ME4600_AO_CTRL_BIT_ENABLE_FIFO |
+			      ME4600_AO_CTRL_BIT_ENABLE_IRQ);
+			outl(tmp, instance->ctrl_reg);
+			tmp |= ME4600_AO_CTRL_BIT_ENABLE_FIFO;
+			outl(tmp, instance->ctrl_reg);
+
+			instance->circ_buf.head = 0;
+			instance->circ_buf.tail = 0;
+			instance->flags &= ~ME4600_AO_FLAGS_BROKEN_PIPE;
+
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			c = ME4600_AO_FIFO_COUNT;
+
+			if (cnt < c)
+				c = cnt;
+
+			for (i = 0; i < c; i++) {
+				if (get_user(value, values)) {
+					PERROR
+					    ("Can't copy value from user space.\n");
+					err = ME_ERRNO_INTERNAL;
+					goto ERROR;
+				}
+
+				if (instance->ao_idx & 0x1)
+					value <<= 16;
+
+				outl(value, instance->fifo_reg);
+
+				values++;
+			}
+
+			cnt -= c;
+
+			ret += c;
+
+			PDEBUG("Wrote %d values to fifo.\n", c);
+
+			while (1) {
+				c = me_circ_buf_space_to_end(&instance->
+							     circ_buf);
+
+				if (c == 0)
+					break;
+
+				if (cnt < c)
+					c = cnt;
+
+				if (c <= 0)
+					break;
+
+				k = sizeof(int) * c;
+
+				k -= copy_from_user(instance->circ_buf.buf +
+						    instance->circ_buf.head,
+						    values, k);
+
+				c = k / sizeof(int);
+
+				PDEBUG("Wrote %d values to circular buffer.\n",
+				       c);
+
+				if (!c) {
+					PERROR
+					    ("Can't copy values from user space.\n");
+					err = ME_ERRNO_INTERNAL;
+					goto ERROR;
+				}
+
+				instance->circ_buf.head =
+				    (instance->circ_buf.head +
+				     c) & (instance->circ_buf.mask);
+
+				values += c;
+				cnt -= c;
+				ret += c;
+			}
+
+			*count = ret;
+
+			break;
+
+		default:
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			PERROR("Invalid write mode specified.\n");
+
+			err = ME_ERRNO_INVALID_WRITE_MODE;
+
+			goto ERROR;
+		}
+
+		break;
+
+	default:		// Single mode of invalid
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+		PERROR("Subdevice is configured in single mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+		goto ERROR;
+	}
+
+      ERROR:
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me4600_ao_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me4600_ao_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	unsigned long tmp;
+	int value;
+	me4600_ao_subdevice_t *instance = dev_id;
+	int i;
+	int c = 0;
+	int c1 = 0;
+
+	if (irq != instance->irq) {
+		PDEBUG("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	if (!((0x1 << (instance->ao_idx + 3)) & inl(instance->irq_status_reg))) {
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	tmp = inl(instance->status_reg);
+
+	if (!(tmp & ME4600_AO_STATUS_BIT_EF) &&
+	    (tmp & ME4600_AO_STATUS_BIT_HF) &&
+	    (tmp & ME4600_AO_STATUS_BIT_HF)) {
+		c = ME4600_AO_FIFO_COUNT;
+		PDEBUG("Fifo empty.\n");
+	} else if ((tmp & ME4600_AO_STATUS_BIT_EF) &&
+		   (tmp & ME4600_AO_STATUS_BIT_HF) &&
+		   (tmp & ME4600_AO_STATUS_BIT_HF)) {
+		c = ME4600_AO_FIFO_COUNT / 2;
+		PDEBUG("Fifo under half full.\n");
+	} else {
+		c = 0;
+		PDEBUG("Fifo full.\n");
+	}
+
+	PDEBUG("Try to write 0x%04X values.\n", c);
+
+	if ((instance->flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK) ==
+	    ME4600_AO_FLAGS_SW_WRAP_MODE_INF) {
+		while (c) {
+			c1 = c;
+
+			if (c1 > (instance->circ_buf.head - instance->circ_buf.tail))	/* Only up to the end of the buffer */
+				c1 = (instance->circ_buf.head -
+				      instance->circ_buf.tail);
+
+			/* Write the values to the FIFO */
+			for (i = 0; i < c1; i++, instance->circ_buf.tail++, c--) {
+				if (instance->ao_idx & 0x1)
+					outl(instance->circ_buf.
+					     buf[instance->circ_buf.tail] << 16,
+					     instance->fifo_reg);
+				else
+					outl(instance->circ_buf.
+					     buf[instance->circ_buf.tail],
+					     instance->fifo_reg);
+			}
+
+			if (instance->circ_buf.tail >= instance->circ_buf.head)	/* Start from beginning */
+				instance->circ_buf.tail = 0;
+		}
+
+		spin_lock(&instance->subdevice_lock);
+
+		tmp = inl(instance->ctrl_reg);
+		tmp |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(tmp, instance->ctrl_reg);
+		tmp &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(tmp, instance->ctrl_reg);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PERROR("Broken pipe.\n");
+			instance->flags |= ME4600_AO_FLAGS_BROKEN_PIPE;
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(tmp, instance->ctrl_reg);
+		}
+
+		spin_unlock(&instance->subdevice_lock);
+	} else if ((instance->flags & ME4600_AO_FLAGS_SW_WRAP_MODE_MASK) ==
+		   ME4600_AO_FLAGS_SW_WRAP_MODE_FIN) {
+		while (c && instance->wrap_remaining) {
+			c1 = c;
+
+			if (c1 > (instance->circ_buf.head - instance->circ_buf.tail))	/* Only up to the end of the buffer */
+				c1 = (instance->circ_buf.head -
+				      instance->circ_buf.tail);
+
+			if (c1 > instance->wrap_remaining)	/* Only up to count of user defined number of values */
+				c1 = instance->wrap_remaining;
+
+			/* Write the values to the FIFO */
+			for (i = 0; i < c1;
+			     i++, instance->circ_buf.tail++, c--,
+			     instance->wrap_remaining--) {
+				if (instance->ao_idx & 0x1)
+					outl(instance->circ_buf.
+					     buf[instance->circ_buf.tail] << 16,
+					     instance->fifo_reg);
+				else
+					outl(instance->circ_buf.
+					     buf[instance->circ_buf.tail],
+					     instance->fifo_reg);
+			}
+
+			if (instance->circ_buf.tail >= instance->circ_buf.head)	/* Start from beginning */
+				instance->circ_buf.tail = 0;
+		}
+
+		spin_lock(&instance->subdevice_lock);
+
+		tmp = inl(instance->ctrl_reg);
+
+		if (!instance->wrap_remaining) {
+			PDEBUG("Finite SW wraparound done.\n");
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		}
+
+		tmp |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+
+		outl(tmp, instance->ctrl_reg);
+		tmp &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(tmp, instance->ctrl_reg);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PERROR("Broken pipe.\n");
+			instance->flags |= ME4600_AO_FLAGS_BROKEN_PIPE;
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(tmp, instance->ctrl_reg);
+		}
+
+		spin_unlock(&instance->subdevice_lock);
+
+	} else {		/* Regular continuous mode */
+
+		while (1) {
+			c1 = me_circ_buf_values_to_end(&instance->circ_buf);
+			PDEBUG("Values to end = %d.\n", c1);
+
+			if (c1 > c)
+				c1 = c;
+
+			if (c1 <= 0) {
+				PDEBUG("Work done or buffer empty.\n");
+				break;
+			}
+
+			if (instance->ao_idx & 0x1) {
+				for (i = 0; i < c1; i++) {
+					value =
+					    *(instance->circ_buf.buf +
+					      instance->circ_buf.tail +
+					      i) << 16;
+					outl(value, instance->fifo_reg);
+				}
+			} else
+				outsl(instance->fifo_reg,
+				      instance->circ_buf.buf +
+				      instance->circ_buf.tail, c1);
+
+			instance->circ_buf.tail =
+			    (instance->circ_buf.tail +
+			     c1) & (instance->circ_buf.mask);
+
+			PDEBUG("%d values wrote to port 0x%04X.\n", c1,
+			       instance->fifo_reg);
+
+			c -= c1;
+		}
+
+		spin_lock(&instance->subdevice_lock);
+
+		tmp = inl(instance->ctrl_reg);
+
+		if (!me_circ_buf_values(&instance->circ_buf)) {
+			PDEBUG
+			    ("Disable Interrupt because no values left in buffer.\n");
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+		}
+
+		tmp |= ME4600_AO_CTRL_BIT_RESET_IRQ;
+
+		outl(tmp, instance->ctrl_reg);
+		tmp &= ~ME4600_AO_CTRL_BIT_RESET_IRQ;
+		outl(tmp, instance->ctrl_reg);
+
+		if (!(inl(instance->status_reg) & ME4600_AO_STATUS_BIT_FSM)) {
+			PDEBUG("Broken pipe in me4600_ao_isr.\n");
+			instance->flags |= ME4600_AO_FLAGS_BROKEN_PIPE;
+			tmp &= ~ME4600_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(tmp, instance->ctrl_reg);
+		}
+
+		spin_unlock(&instance->subdevice_lock);
+
+		wake_up_interruptible(&instance->wait_queue);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void me4600_ao_destructor(struct me_subdevice *subdevice)
+{
+	me4600_ao_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	free_irq(instance->irq, instance);
+	kfree(instance->circ_buf.buf);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+me4600_ao_subdevice_t *me4600_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     int ao_idx, int fifo, int irq)
+{
+	me4600_ao_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me4600_ao_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_ao_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->preload_reg_lock = preload_reg_lock;
+	subdevice->preload_flags = preload_flags;
+
+	/* Allocate and initialize circular buffer */
+	subdevice->circ_buf.mask = ME4600_AO_CIRC_BUF_COUNT - 1;
+	subdevice->circ_buf.buf = kmalloc(ME4600_AO_CIRC_BUF_SIZE, GFP_KERNEL);
+
+	if (!subdevice->circ_buf.buf) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		me_subdevice_deinit((me_subdevice_t *) subdevice);
+		kfree(subdevice);
+		return NULL;
+	}
+
+	memset(subdevice->circ_buf.buf, 0, ME4600_AO_CIRC_BUF_SIZE);
+
+	subdevice->circ_buf.head = 0;
+	subdevice->circ_buf.tail = 0;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Initialize single value to 0V */
+	subdevice->single_value = 0x8000;
+
+	/* Store analog output index */
+	subdevice->ao_idx = ao_idx;
+
+	/* Store if analog output has fifo */
+	subdevice->fifo = fifo;
+
+	/* Initialize registers */
+
+	if (ao_idx == 0) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_00_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_00_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_00_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_00_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_00_TIMER_REG;
+		subdevice->reg_base = reg_base;
+
+		if (inl(subdevice->reg_base + ME4600_AO_BOSCH_REG) == 0x20000) {
+			PINFO("Bosch firmware in use for channel 0.\n");
+			subdevice->bosch_fw = 1;
+		} else {
+			subdevice->bosch_fw = 0;
+		}
+	} else if (ao_idx == 1) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_01_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_01_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_01_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_01_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_01_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bosch_fw = 0;
+	} else if (ao_idx == 2) {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_02_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_02_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_02_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_02_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_02_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bosch_fw = 0;
+	} else {
+		subdevice->ctrl_reg = reg_base + ME4600_AO_03_CTRL_REG;
+		subdevice->status_reg = reg_base + ME4600_AO_03_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME4600_AO_03_FIFO_REG;
+		subdevice->single_reg = reg_base + ME4600_AO_03_SINGLE_REG;
+		subdevice->timer_reg = reg_base + ME4600_AO_03_TIMER_REG;
+		subdevice->reg_base = reg_base;
+		subdevice->bosch_fw = 0;
+	}
+
+	subdevice->irq_status_reg = reg_base + ME4600_IRQ_STATUS_REG;
+	subdevice->preload_reg = reg_base + ME4600_AO_LOADSETREG_XX;
+
+	/* Register interrupt service routine */
+	subdevice->irq = irq;
+
+	if (request_irq
+	    (subdevice->irq, me4600_ao_isr, SA_INTERRUPT | SA_SHIRQ,
+	     ME4600_NAME, subdevice)) {
+		PERROR("Cannot get interrupt line.\n");
+		me_subdevice_deinit((me_subdevice_t *) subdevice);
+		kfree(subdevice->circ_buf.buf);
+		kfree(subdevice);
+		return NULL;
+	}
+
+	/* Override base class methods. */
+	subdevice->base.me_subdevice_destructor = me4600_ao_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_ao_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_ao_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_ao_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me4600_ao_io_single_write;
+	subdevice->base.me_subdevice_io_stream_config =
+	    me4600_ao_io_stream_config;
+	subdevice->base.me_subdevice_io_stream_new_values =
+	    me4600_ao_io_stream_new_values;
+	subdevice->base.me_subdevice_io_stream_write =
+	    me4600_ao_io_stream_write;
+	subdevice->base.me_subdevice_io_stream_start =
+	    me4600_ao_io_stream_start;
+	subdevice->base.me_subdevice_io_stream_status =
+	    me4600_ao_io_stream_status;
+	subdevice->base.me_subdevice_io_stream_stop = me4600_ao_io_stream_stop;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_ao_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_ao_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_ao_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me4600_ao_query_subdevice_caps_args;
+	subdevice->base.me_subdevice_query_range_by_min_max =
+	    me4600_ao_query_range_by_min_max;
+	subdevice->base.me_subdevice_query_number_ranges =
+	    me4600_ao_query_number_ranges;
+	subdevice->base.me_subdevice_query_range_info =
+	    me4600_ao_query_range_info;
+	subdevice->base.me_subdevice_query_timer = me4600_ao_query_timer;
+
+	return subdevice;
+}
+
+#endif // BOSCH
+
+/* Common functions
+*/
+
+static int me4600_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((*max - *min) < 0) {
+		PERROR("Invalid minimum and maximum values specified.\n");
+		return ME_ERRNO_INVALID_MIN_MAX;
+	}
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		if ((*max <= (ME4600_AO_MAX_RANGE + 1000))
+		    && (*min >= ME4600_AO_MIN_RANGE)) {
+			*min = ME4600_AO_MIN_RANGE;
+			*max = ME4600_AO_MAX_RANGE;
+			*maxdata = ME4600_AO_MAX_DATA;
+			*range = 0;
+		} else {
+			PERROR("No matching range available.\n");
+			return ME_ERRNO_NO_RANGE;
+		}
+	} else {
+		PERROR("Invalid physical unit specified.\n");
+		return ME_ERRNO_INVALID_UNIT;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		*count = 1;
+	} else {
+		*count = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (range == 0) {
+		*unit = ME_UNIT_VOLT;
+		*min = ME4600_AO_MIN_RANGE;
+		*max = ME4600_AO_MAX_RANGE;
+		*maxdata = ME4600_AO_MAX_DATA;
+	} else {
+		PERROR("Invalid range number specified.\n");
+		return ME_ERRNO_INVALID_RANGE;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((timer != ME_TIMER_ACQ_START) && (timer != ME_TIMER_CONV_START)) {
+		PERROR("Invalid timer specified.\n");
+		return ME_ERRNO_INVALID_TIMER;
+	}
+
+	if (instance->fifo) {	//Streaming device.
+		*base_frequency = ME4600_AO_BASE_FREQUENCY;
+		if (timer == ME_TIMER_ACQ_START) {
+			*min_ticks = ME4600_AO_MIN_ACQ_TICKS;
+			*max_ticks = ME4600_AO_MAX_ACQ_TICKS;
+		} else if (timer == ME_TIMER_CONV_START) {
+			*min_ticks = ME4600_AO_MIN_CHAN_TICKS;
+			*max_ticks = ME4600_AO_MAX_CHAN_TICKS;
+		}
+	} else {		//Not streaming device!
+		*base_frequency = 0;
+		*min_ticks = 0;
+		*max_ticks = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	me4600_ao_subdevice_t *instance;
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*number = 1;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	me4600_ao_subdevice_t *instance;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*type = ME_TYPE_AO;
+	*subtype = (instance->fifo) ? ME_SUBTYPE_STREAMING : ME_SUBTYPE_SINGLE;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	me4600_ao_subdevice_t *instance;
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*caps =
+	    ME_CAPS_AO_TRIG_SYNCHRONOUS | ((instance->fifo) ? ME_CAPS_AO_FIFO :
+					   ME_CAPS_NONE);
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ao_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count)
+{
+	me4600_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me4600_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (count != 1) {
+		PERROR("Invalid capability argument count.\n");
+		return ME_ERRNO_INVALID_CAP_ARG_COUNT;
+	}
+
+	switch (cap) {
+	case ME_CAP_AI_FIFO_SIZE:
+		args[0] = (instance->fifo) ? ME4600_AO_FIFO_COUNT : 0;
+		break;
+
+	case ME_CAP_AI_BUFFER_SIZE:
+		args[0] =
+		    (instance->circ_buf.buf) ? ME4600_AO_CIRC_BUF_COUNT : 0;
+		break;
+
+	default:
+		PERROR("Invalid capability.\n");
+		err = ME_ERRNO_INVALID_CAP;
+		args[0] = 0;
+	}
+
+	return err;
+}

+ 263 - 0
drivers/staging/meilhaus/me4600_ao.h

@@ -0,0 +1,263 @@
+/**
+ * @file me4600_ao.h
+ *
+ * @brief Meilhaus ME-4000 analog output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_AO_H_
+# define _ME4600_AO_H_
+
+# include <linux/version.h>
+# include "mesubdevice.h"
+# include "mecirc_buf.h"
+# include "meioctl.h"
+
+# ifdef __KERNEL__
+
+#  ifdef BOSCH
+#   undef ME_SYNAPSE
+#   ifndef _CBUFF_32b_t
+# 	 define _CBUFF_32b_t
+#   endif //_CBUFF_32b_t
+#  endif //BOSCH
+
+#  define ME4600_AO_MAX_SUBDEVICES		4
+#  define ME4600_AO_FIFO_COUNT			4096
+
+#  define ME4600_AO_BASE_FREQUENCY		33000000LL
+
+#  define ME4600_AO_MIN_ACQ_TICKS		0LL
+#  define ME4600_AO_MAX_ACQ_TICKS		0LL
+
+#  define ME4600_AO_MIN_CHAN_TICKS		66LL
+#  define ME4600_AO_MAX_CHAN_TICKS		0xFFFFFFFFLL
+
+#  define ME4600_AO_MIN_RANGE			-10000000
+#  define ME4600_AO_MAX_RANGE			9999694
+
+#  define ME4600_AO_MAX_DATA			0xFFFF
+
+#  ifdef ME_SYNAPSE
+#   define ME4600_AO_CIRC_BUF_SIZE_ORDER 		8	// 2^n PAGES =>> Maximum value of 1MB for Synapse
+#  else
+#   define ME4600_AO_CIRC_BUF_SIZE_ORDER 		5	// 2^n PAGES =>> 128KB
+#  endif
+#  define ME4600_AO_CIRC_BUF_SIZE 		PAGE_SIZE<<ME4600_AO_CIRC_BUF_SIZE_ORDER	// Buffer size in bytes.
+
+#  ifdef _CBUFF_32b_t
+#   define ME4600_AO_CIRC_BUF_COUNT	((ME4600_AO_CIRC_BUF_SIZE) / sizeof(uint32_t))	// Size in values
+#  else
+#   define ME4600_AO_CIRC_BUF_COUNT	((ME4600_AO_CIRC_BUF_SIZE) / sizeof(uint16_t))	// Size in values
+#  endif
+
+#  define ME4600_AO_CONTINOUS					0x0
+#  define ME4600_AO_WRAP_MODE					0x1
+#  define ME4600_AO_HW_MODE						0x2
+
+#  define ME4600_AO_HW_WRAP_MODE				(ME4600_AO_WRAP_MODE | ME4600_AO_HW_MODE)
+#  define ME4600_AO_SW_WRAP_MODE				ME4600_AO_WRAP_MODE
+
+#  define ME4600_AO_INF_STOP_MODE				0x0
+#  define ME4600_AO_ACQ_STOP_MODE				0x1
+#  define ME4600_AO_SCAN_STOP_MODE				0x2
+
+#  ifdef BOSCH			//SPECIAL BUILD FOR BOSCH
+
+/* Bits for flags attribute. */
+#   define ME4600_AO_FLAGS_BROKEN_PIPE			0x1
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_0		0x2
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_1		0x4
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_MASK	(ME4600_AO_FLAGS_SW_WRAP_MODE_0 | ME4600_AO_FLAGS_SW_WRAP_MODE_1)
+
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_NONE	0x0
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_INF		0x2
+#   define ME4600_AO_FLAGS_SW_WRAP_MODE_FIN		0x4
+
+	/**
+	* @brief The ME-4000 analog output subdevice class.
+	*/
+typedef struct me4600_ao_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;				/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;			/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *preload_reg_lock;		/**< Spin lock to protect #preload_reg from concurrent access. */
+	uint32_t *preload_flags;
+
+	unsigned int irq;					/**< The interrupt request number assigned by the PCI BIOS. */
+	me_circ_buf_t circ_buf;				/**< Circular buffer holding measurment data. */
+	wait_queue_head_t wait_queue;		/**< Wait queue to put on tasks waiting for data to arrive. */
+
+	int single_value;					/**< Mirror of the value written in single mode. */
+
+	int volatile flags;					/**< Flags used for storing SW wraparound setup and error signalling from ISR. */
+	unsigned int wrap_count;			/**< The user defined wraparound cycle count. */
+	unsigned int wrap_remaining;		/**< The wraparound cycle down counter used by a running conversion. */
+	unsigned int ao_idx;				/**< The index of this analog output on this device. */
+	int fifo;							/**< If set this device has a FIFO. */
+
+	int bosch_fw;						/**< If set the bosch firmware is in PROM. */
+
+	/* Registers */
+	uint32_t ctrl_reg;
+	uint32_t status_reg;
+	uint32_t fifo_reg;
+	uint32_t single_reg;
+	uint32_t timer_reg;
+	uint32_t irq_status_reg;
+	uint32_t preload_reg;
+	uint32_t reg_base;
+} me4600_ao_subdevice_t;
+
+	/**
+	* @brief The constructor to generate a ME-4000 analog output subdevice instance for BOSCH project.
+	*
+	* @param reg_base The register base address of the device as returned by the PCI BIOS.
+	* @param ctrl_reg_lock Pointer to spin lock protecting the control register from concurrent access.
+	* @param preload_flags Pointer to spin lock protecting the hold&trigger register from concurrent access.
+	* @param ao_idx Subdevice number.
+	* @param fifo Flag set if subdevice has hardware FIFO.
+	* @param irq IRQ number.
+	*
+	* @return Pointer to new instance on success.\n
+	* NULL on error.
+	*/
+me4600_ao_subdevice_t *me4600_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     int ao_idx, int fifo, int irq);
+
+#  else	//~BOSCH
+
+//ME4600_AO_FLAGS_BROKEN_PIPE is OBSOLETE => Now problems are reported in status.
+
+typedef enum ME4600_AO_STATUS {
+	ao_status_none = 0,
+	ao_status_single_configured,
+	ao_status_single_run_wait,
+	ao_status_single_run,
+	ao_status_single_end_wait,
+	ao_status_single_end,
+	ao_status_stream_configured,
+	ao_status_stream_run_wait,
+	ao_status_stream_run,
+	ao_status_stream_end_wait,
+	ao_status_stream_end,
+	ao_status_stream_fifo_error,
+	ao_status_stream_buffer_error,
+	ao_status_stream_error,
+	ao_status_last
+} ME4600_AO_STATUS;
+
+typedef struct me4600_ao_timeout {
+	unsigned long start_time;
+	unsigned long delay;
+} me4600_ao_timeout_t;
+
+	/**
+	* @brief The ME-4600 analog output subdevice class.
+	*/
+typedef struct me4600_ao_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;						/**< The subdevice base class. */
+	unsigned int ao_idx;						/**< The index of this analog output on this device. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;					/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *preload_reg_lock;				/**< Spin lock to protect preload_reg from concurrent access. */
+
+	uint32_t *preload_flags;
+
+	/* Hardware feautres */
+	unsigned int irq;							/**< The interrupt request number assigned by the PCI BIOS. */
+	int fifo;									/**< If set this device has a FIFO. */
+	int bitpattern;								/**< If set this device use bitpattern. */
+
+	int single_value;							/**< Mirror of the output value in single mode. */
+	int single_value_in_fifo;					/**< Mirror of the value written in single mode. */
+	uint32_t ctrl_trg;							/**< Mirror of the trigger settings. */
+
+	volatile int mode;							/**< Flags used for storing SW wraparound setup*/
+	int stop_mode;								/**< The user defined stop condition flag. */
+	unsigned int start_mode;
+	unsigned int stop_count;					/**< The user defined dates presentation end count. */
+	unsigned int stop_data_count;				/**< The stop presentation count. */
+	unsigned int data_count;					/**< The real presentation count. */
+	unsigned int preloaded_count;				/**< The next data addres in buffer. <= for wraparound mode. */
+	int hardware_stop_delay;					/**< The time that stop can take. This is only to not show hardware bug to user. */
+
+	volatile enum ME4600_AO_STATUS status;		/**< The current stream status flag. */
+	me4600_ao_timeout_t timeout;				/**< The timeout for start in blocking and non-blocking mode. */
+
+										/* Registers *//**< All registers are 32 bits long. */
+	unsigned long ctrl_reg;
+	unsigned long status_reg;
+	unsigned long fifo_reg;
+	unsigned long single_reg;
+	unsigned long timer_reg;
+	unsigned long irq_status_reg;
+	unsigned long preload_reg;
+	unsigned long reg_base;
+
+	/* Software buffer */
+	me_circ_buf_t circ_buf;						/**< Circular buffer holding measurment data. 32 bit long */
+	wait_queue_head_t wait_queue;				/**< Wait queue to put on tasks waiting for data to arrive. */
+
+	struct workqueue_struct *me4600_workqueue;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct ao_control_task;
+#else
+	struct delayed_work ao_control_task;
+#endif
+
+	volatile int ao_control_task_flag;			/**< Flag controling reexecuting of control task */
+
+} me4600_ao_subdevice_t;
+
+	/**
+	* @brief The constructor to generate a ME-4600 analog output subdevice instance.
+	*
+	* @param reg_base The register base address of the device as returned by the PCI BIOS.
+	* @param ctrl_reg_lock Pointer to spin lock protecting the control register from concurrent access.
+	* @param preload_flags Pointer to spin lock protecting the hold&trigger register from concurrent access.
+	* @param ao_idx Subdevice number.
+	* @param fifo Flag set if subdevice has hardware FIFO.
+	* @param irq IRQ number.
+	* @param me4600_wq Queue for asynchronous task (1 queue for all subdevice on 1 board).
+	*
+	* @return Pointer to new instance on success.\n
+	* NULL on error.
+	*/
+me4600_ao_subdevice_t *me4600_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     int ao_idx,
+					     int fifo,
+					     int irq,
+					     struct workqueue_struct
+					     *me4600_wq);
+
+#  endif //BOSCH
+# endif	//__KERNEL__
+#endif // ~_ME4600_AO_H_

+ 113 - 0
drivers/staging/meilhaus/me4600_ao_reg.h

@@ -0,0 +1,113 @@
+/**
+ * @file me4600_ao_reg.h
+ *
+ * @brief ME-4000 analog output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_AO_REG_H_
+#define _ME4600_AO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME4600_AO_00_CTRL_REG					0x00	// R/W
+#define ME4600_AO_00_STATUS_REG					0x04	// R/_
+#define ME4600_AO_00_FIFO_REG					0x08	// _/W
+#define ME4600_AO_00_SINGLE_REG					0x0C	// R/W
+#define ME4600_AO_00_TIMER_REG					0x10	// _/W
+
+#define ME4600_AO_01_CTRL_REG					0x18	// R/W
+#define ME4600_AO_01_STATUS_REG					0x1C	// R/_
+#define ME4600_AO_01_FIFO_REG					0x20	// _/W
+#define ME4600_AO_01_SINGLE_REG					0x24	// R/W
+#define ME4600_AO_01_TIMER_REG					0x28	// _/W
+
+#define ME4600_AO_02_CTRL_REG					0x30	// R/W
+#define ME4600_AO_02_STATUS_REG					0x34	// R/_
+#define ME4600_AO_02_FIFO_REG					0x38	// _/W
+#define ME4600_AO_02_SINGLE_REG					0x3C	// R/W
+#define ME4600_AO_02_TIMER_REG					0x40	// _/W
+
+#define ME4600_AO_03_CTRL_REG					0x48	// R/W
+#define ME4600_AO_03_STATUS_REG					0x4C	// R/_
+#define ME4600_AO_03_FIFO_REG					0x50	// _/W
+#define ME4600_AO_03_SINGLE_REG					0x54	// R/W
+#define ME4600_AO_03_TIMER_REG					0x58	// _/W
+
+#define ME4600_AO_DEMUX_ADJUST_REG				0xBC	// -/W
+#define ME4600_AO_DEMUX_ADJUST_VALUE			0x4C
+
+#ifdef BOSCH
+# define ME4600_AO_BOSCH_REG					0xC4
+
+# define ME4600_AO_LOADSETREG_XX				0xB4	// R/W
+
+# define ME4600_AO_CTRL_BIT_MODE_0				0x001
+# define ME4600_AO_CTRL_BIT_MODE_1				0x002
+# define ME4600_AO_CTRL_MASK_MODE				0x003
+
+#else //~BOSCH
+
+#define ME4600_AO_SYNC_REG						0xB4	// R/W    ///ME4600_AO_SYNC_REG <==> ME4600_AO_PRELOAD_REG <==> ME4600_AO_LOADSETREG_XX
+
+# define ME4600_AO_MODE_SINGLE					0x00000000
+# define ME4600_AO_MODE_WRAPAROUND				0x00000001
+# define ME4600_AO_MODE_CONTINUOUS				0x00000002
+# define ME4600_AO_CTRL_MODE_MASK				(ME4600_AO_MODE_WRAPAROUND | ME4600_AO_MODE_CONTINUOUS)
+#endif //BOSCH
+
+#define ME4600_AO_CTRL_BIT_MODE_WRAPAROUND		ME4600_AO_MODE_WRAPAROUND
+#define ME4600_AO_CTRL_BIT_MODE_CONTINOUS		ME4600_AO_MODE_CONTINUOUS
+#define ME4600_AO_CTRL_BIT_STOP					0x00000004
+#define ME4600_AO_CTRL_BIT_ENABLE_FIFO			0x00000008
+#define ME4600_AO_CTRL_BIT_ENABLE_EX_TRIG		0x00000010
+#define ME4600_AO_CTRL_BIT_EX_TRIG_EDGE			0x00000020
+#define ME4600_AO_CTRL_BIT_IMMEDIATE_STOP		0x00000080
+#define ME4600_AO_CTRL_BIT_ENABLE_DO			0x00000100
+#define ME4600_AO_CTRL_BIT_ENABLE_IRQ			0x00000200
+#define ME4600_AO_CTRL_BIT_RESET_IRQ			0x00000400
+#define ME4600_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH	0x00000800
+/*
+#define ME4600_AO_SYNC_HOLD_0					0x00000001
+#define ME4600_AO_SYNC_HOLD_1					0x00000002
+#define ME4600_AO_SYNC_HOLD_2					0x00000004
+#define ME4600_AO_SYNC_HOLD_3					0x00000008
+*/
+#define ME4600_AO_SYNC_HOLD						0x00000001
+
+/*
+#define ME4600_AO_SYNC_EXT_TRIG_0				0x00010000
+#define ME4600_AO_SYNC_EXT_TRIG_1				0x00020000
+#define ME4600_AO_SYNC_EXT_TRIG_2				0x00040000
+#define ME4600_AO_SYNC_EXT_TRIG_3				0x00080000
+*/
+#define ME4600_AO_SYNC_EXT_TRIG					0x00010000
+
+#define ME4600_AO_EXT_TRIG						0x80000000
+
+#define ME4600_AO_STATUS_BIT_FSM				0x00000001
+#define ME4600_AO_STATUS_BIT_FF					0x00000002
+#define ME4600_AO_STATUS_BIT_HF					0x00000004
+#define ME4600_AO_STATUS_BIT_EF					0x00000008
+
+#endif
+#endif

+ 373 - 0
drivers/staging/meilhaus/me4600_device.c

@@ -0,0 +1,373 @@
+/**
+ * @file me4600_device.c
+ *
+ * @brief ME-4600 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "me4600_device.h"
+#include "meplx_reg.h"
+
+#include "mefirmware.h"
+
+#include "mesubdevice.h"
+#include "me4600_do.h"
+#include "me4600_di.h"
+#include "me4600_dio.h"
+#include "me8254.h"
+#include "me4600_ai.h"
+#include "me4600_ao.h"
+#include "me4600_ext_irq.h"
+
+/**
+ * @brief Global variable.
+ * This is working queue for runing a separate atask that will be responsible for work status (start, stop, timeouts).
+ */
+static struct workqueue_struct *me4600_workqueue;
+
+#ifdef BOSCH
+me_device_t *me4600_pci_constructor(struct pci_dev *pci_device, int me_bosch_fw)
+#else //~BOSCH
+me_device_t *me4600_pci_constructor(struct pci_dev *pci_device)
+#endif				//BOSCH
+{
+	me4600_device_t *me4600_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me4600_device = kmalloc(sizeof(me4600_device_t), GFP_KERNEL);
+
+	if (!me4600_device) {
+		PERROR("Cannot get memory for ME-4600 device instance.\n");
+		return NULL;
+	}
+
+	memset(me4600_device, 0, sizeof(me4600_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me4600_device, pci_device);
+
+	if (err) {
+		kfree(me4600_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+	// Download the xilinx firmware.
+	if (me4600_device->base.info.pci.device_id == PCI_DEVICE_ID_MEILHAUS_ME4610) {	//Jekyll <=> me4610
+		err =
+		    me_xilinx_download(me4600_device->base.info.pci.
+				       reg_bases[1],
+				       me4600_device->base.info.pci.
+				       reg_bases[5], &pci_device->dev,
+				       "me4610.bin");
+	} else {		// General me4600 firmware
+#ifdef BOSCH
+		err =
+		    me_xilinx_download(me4600_device->base.info.pci.
+				       reg_bases[1],
+				       me4600_device->base.info.pci.
+				       reg_bases[5], &pci_device->dev,
+				       (me_bosch_fw) ? "me4600_bosch.bin" :
+				       "me4600.bin");
+#else //~BOSCH
+		err =
+		    me_xilinx_download(me4600_device->base.info.pci.
+				       reg_bases[1],
+				       me4600_device->base.info.pci.
+				       reg_bases[5], &pci_device->dev,
+				       "me4600.bin");
+#endif
+	}
+
+	if (err) {
+		me_device_deinit((me_device_t *) me4600_device);
+		kfree(me4600_device);
+		PERROR("Cannot download firmware.\n");
+		return NULL;
+	}
+	// Get the index in the device version information table.
+	version_idx =
+	    me4600_versions_get_device_index(me4600_device->base.info.pci.
+					     device_id);
+
+	// Initialize spin locks.
+	spin_lock_init(&me4600_device->preload_reg_lock);
+
+	me4600_device->preload_flags = 0;
+
+	spin_lock_init(&me4600_device->dio_lock);
+	spin_lock_init(&me4600_device->ai_ctrl_lock);
+	spin_lock_init(&me4600_device->ctr_ctrl_reg_lock);
+	spin_lock_init(&me4600_device->ctr_clk_src_reg_lock);
+
+	// Create digital input instances.
+	for (i = 0; i < me4600_versions[version_idx].di_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me4600_di_constructor(me4600_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     &me4600_device->
+							     dio_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create digital output instances.
+	for (i = 0; i < me4600_versions[version_idx].do_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me4600_do_constructor(me4600_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     &me4600_device->
+							     dio_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create digital input/output instances.
+	for (i = 0; i < me4600_versions[version_idx].dio_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me4600_dio_constructor(me4600_device->
+							      base.info.pci.
+							      reg_bases[2],
+							      me4600_versions
+							      [version_idx].
+							      do_subdevices +
+							      me4600_versions
+							      [version_idx].
+							      di_subdevices + i,
+							      &me4600_device->
+							      dio_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create analog input instances.
+	for (i = 0; i < me4600_versions[version_idx].ai_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me4600_ai_constructor(me4600_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     me4600_versions
+							     [version_idx].
+							     ai_channels,
+							     me4600_versions
+							     [version_idx].
+							     ai_ranges,
+							     me4600_versions
+							     [version_idx].
+							     ai_isolated,
+							     me4600_versions
+							     [version_idx].
+							     ai_sh,
+							     me4600_device->
+							     base.irq,
+							     &me4600_device->
+							     ai_ctrl_lock,
+							     me4600_workqueue);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create analog output instances.
+	for (i = 0; i < me4600_versions[version_idx].ao_subdevices; i++) {
+#ifdef BOSCH
+		subdevice =
+		    (me_subdevice_t *) me4600_ao_constructor(me4600_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     &me4600_device->
+							     preload_reg_lock,
+							     &me4600_device->
+							     preload_flags, i,
+							     me4600_versions
+							     [version_idx].
+							     ao_fifo,
+							     me4600_device->
+							     base.irq);
+#else //~BOSCH
+		subdevice =
+		    (me_subdevice_t *) me4600_ao_constructor(me4600_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     &me4600_device->
+							     preload_reg_lock,
+							     &me4600_device->
+							     preload_flags, i,
+							     me4600_versions
+							     [version_idx].
+							     ao_fifo,
+							     me4600_device->
+							     base.irq,
+							     me4600_workqueue);
+#endif
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create counter instances.
+	for (i = 0; i < me4600_versions[version_idx].ctr_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8254_constructor(me4600_device->base.
+							  info.pci.device_id,
+							  me4600_device->base.
+							  info.pci.reg_bases[3],
+							  0, i,
+							  &me4600_device->
+							  ctr_ctrl_reg_lock,
+							  &me4600_device->
+							  ctr_clk_src_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	// Create external interrupt instances.
+	for (i = 0; i < me4600_versions[version_idx].ext_irq_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *)
+		    me4600_ext_irq_constructor(me4600_device->base.info.pci.
+					       reg_bases[2],
+					       me4600_device->base.irq,
+					       &me4600_device->ai_ctrl_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me4600_device);
+			kfree(me4600_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me4600_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me4600_device;
+}
+
+// Init and exit of module.
+
+static int __init me4600_init(void)
+{
+	PDEBUG("executed.\n");
+
+#ifndef BOSCH
+	me4600_workqueue = create_singlethread_workqueue("me4600");
+#endif
+	return 0;
+}
+
+static void __exit me4600_exit(void)
+{
+	PDEBUG("executed.\n");
+
+#ifndef BOSCH
+	flush_workqueue(me4600_workqueue);
+	destroy_workqueue(me4600_workqueue);
+#endif
+}
+
+module_init(me4600_init);
+module_exit(me4600_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for ME-46xx Devices");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-46xx Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me4600_pci_constructor);

+ 151 - 0
drivers/staging/meilhaus/me4600_device.h

@@ -0,0 +1,151 @@
+/**
+ * @file me4600_device.h
+ *
+ * @brief ME-4600 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_DEVICE_H
+#define _ME4600_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-4600 device capabilities.
+ */
+typedef struct me4600_version {
+	uint16_t device_id;
+	unsigned int do_subdevices;
+	unsigned int di_subdevices;
+	unsigned int dio_subdevices;
+	unsigned int ctr_subdevices;
+	unsigned int ai_subdevices;
+	unsigned int ai_channels;
+	unsigned int ai_ranges;
+	unsigned int ai_isolated;
+	unsigned int ai_sh;
+	unsigned int ao_subdevices;
+	unsigned int ao_fifo;	//How many devices have FIFO
+	unsigned int ext_irq_subdevices;
+} me4600_version_t;
+
+/**
+ * @brief ME-4600 device capabilities.
+ */
+static me4600_version_t me4600_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME4610, 0, 0, 4, 3, 1, 16, 1, 0, 0, 0, 0, 1},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME4650, 0, 0, 4, 0, 1, 16, 4, 0, 0, 0, 0, 1},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME4660, 0, 0, 4, 3, 1, 16, 4, 0, 0, 2, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4660I, 1, 1, 2, 3, 1, 16, 4, 1, 0, 2, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4660S, 0, 0, 4, 3, 1, 16, 4, 0, 1, 2, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4660IS, 1, 1, 2, 3, 1, 16, 4, 1, 1, 2, 0, 1},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME4670, 0, 0, 4, 3, 1, 32, 4, 0, 0, 4, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4670I, 1, 1, 2, 3, 1, 32, 4, 1, 0, 4, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4670S, 0, 0, 4, 3, 1, 32, 4, 0, 1, 4, 0, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4670IS, 1, 1, 2, 3, 1, 32, 4, 1, 1, 4, 0, 1},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME4680, 0, 0, 4, 3, 1, 32, 4, 0, 0, 4, 4, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4680I, 1, 1, 2, 3, 1, 32, 4, 1, 0, 4, 4, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4680S, 0, 0, 4, 3, 1, 32, 4, 0, 1, 4, 4, 1},
+	{PCI_DEVICE_ID_MEILHAUS_ME4680IS, 1, 1, 2, 3, 1, 32, 4, 1, 1, 4, 4, 1},
+
+	{0},
+};
+
+#define ME4600_DEVICE_VERSIONS (sizeof(me4600_versions) / sizeof(me4600_version_t) - 1)	/**< Returns the number of entries in #me4600_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me4600_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me4600_versions.
+ */
+static inline unsigned int me4600_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME4600_DEVICE_VERSIONS; i++)
+		if (me4600_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-4600 device class structure.
+ */
+typedef struct me4600_device {
+	me_device_t base;					/**< The Meilhaus device base class. */
+
+	/* Child class attributes. */
+	spinlock_t preload_reg_lock;		/**< Guards the preload register of the anaolog output devices. */
+	unsigned int preload_flags;			/**< Used in conjunction with #preload_reg_lock. */
+	spinlock_t dio_lock;				/**< Locks the control register of the digital input/output subdevices. */
+	spinlock_t ai_ctrl_lock;			/**< Locks the control register of the analog input subdevice. */
+	spinlock_t ctr_ctrl_reg_lock;		/**< Locks the counter control register. */
+	spinlock_t ctr_clk_src_reg_lock;	/**< Not used on this device but needed for the me8254 subdevice constructor call. */
+} me4600_device_t;
+
+/**
+ * @brief The ME-4600 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ * @param me_bosch_fw If set the device shall use the bosch firmware. (Only for special BOSCH build)
+ *
+ * @return On succes a new ME-4600 device instance. \n
+ *         NULL on error.
+ */
+
+#ifdef BOSCH
+/**
+ * @brief The ME-4600 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ * @param me_bosch_fw If set the device shall use the bosch firmware.
+ *
+ * @return On succes a new ME-4600 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me4600_pci_constructor(struct pci_dev *pci_device, int me_bosch_fw)
+    __attribute__ ((weak));
+#else //~BOSCH
+/**
+ * @brief The ME-4600 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-4600 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me4600_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+#endif
+
+#endif
+#endif

+ 256 - 0
drivers/staging/meilhaus/me4600_di.c

@@ -0,0 +1,256 @@
+/**
+ * @file me4600_di.c
+ *
+ * @brief ME-4000 digital input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me4600_dio_reg.h"
+#include "me4600_di.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me4600_di_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me4600_di_subdevice_t *instance;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_di_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inl(instance->ctrl_reg);
+	mode &= ~(ME4600_DIO_CTRL_BIT_MODE_2 | ME4600_DIO_CTRL_BIT_MODE_3);	//0xFFF3
+	outl(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outl(0, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_di_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	int err = ME_ERRNO_SUCCESS;
+	me4600_di_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+			} else {
+				PERROR("Invalid port direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_di_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me4600_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inl(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inl(instance->port_reg) & 0xFF;
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_di_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_di_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_di_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me4600_di_subdevice_t *me4600_di_constructor(uint32_t reg_base,
+					     spinlock_t * ctrl_reg_lock)
+{
+	me4600_di_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me4600_di_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_di_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the subdevice index */
+	subdevice->port_reg = reg_base + ME4600_DIO_PORT_1_REG;
+	subdevice->ctrl_reg = reg_base + ME4600_DIO_CTRL_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_di_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_di_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_di_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_di_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_di_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_di_query_subdevice_caps;
+
+	return subdevice;
+}

+ 64 - 0
drivers/staging/meilhaus/me4600_di.h

@@ -0,0 +1,64 @@
+/**
+ * @file me4600_di.h
+ *
+ * @brief ME-4000 digital input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_DI_H_
+#define _ME4600_DI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me4600_di_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me4600_di_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-4000 digital input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me4600_di_subdevice_t *me4600_di_constructor(uint32_t reg_base,
+					     spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 510 - 0
drivers/staging/meilhaus/me4600_dio.c

@@ -0,0 +1,510 @@
+/**
+ * @file me4600_dio.c
+ *
+ * @brief ME-4000 digital input/output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me4600_dio_reg.h"
+#include "me4600_dio.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me4600_dio_io_reset_subdevice(struct me_subdevice *subdevice,
+					 struct file *filep, int flags)
+{
+	me4600_dio_subdevice_t *instance;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_dio_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	/* Set port to input mode */
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inl(instance->ctrl_reg);
+	mode &=
+	    ~((ME4600_DIO_CTRL_BIT_MODE_0 | ME4600_DIO_CTRL_BIT_MODE_1) <<
+	      (instance->dio_idx * 2));
+	outl(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outl(0, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_dio_io_single_config(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int single_config,
+				       int ref,
+				       int trig_chan,
+				       int trig_type, int trig_edge, int flags)
+{
+	me4600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+	uint32_t size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+	uint32_t mask;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inl(instance->ctrl_reg);
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+				mode &=
+				    ~((ME4600_DIO_CTRL_BIT_MODE_0 |
+				       ME4600_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				mode &=
+				    ~((ME4600_DIO_CTRL_BIT_MODE_0 |
+				       ME4600_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+				mode |=
+				    ME4600_DIO_CTRL_BIT_MODE_0 << (instance->
+								   dio_idx * 2);
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_MUX32M) {
+				mask =
+				    (ME4600_DIO_CTRL_BIT_MODE_0 |
+				     ME4600_DIO_CTRL_BIT_MODE_1) << (instance->
+								     dio_idx *
+								     2);
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+				    ME4600_DIO_CTRL_BIT_FUNCTION_1;
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+				    instance->dio_idx;
+				mode &= ~mask;
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+					mode |= ME4600_DIO_CTRL_BIT_FUNCTION_1;
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+					mode |= ME4600_DIO_CTRL_BIT_FUNCTION_1;
+					mode |=
+					    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+					    instance->dio_idx;
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else if (single_config ==
+				   ME_SINGLE_CONFIG_DIO_DEMUX32) {
+				mask =
+				    (ME4600_DIO_CTRL_BIT_MODE_0 |
+				     ME4600_DIO_CTRL_BIT_MODE_1) << (instance->
+								     dio_idx *
+								     2);
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+				    ME4600_DIO_CTRL_BIT_FUNCTION_1;
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+				    instance->dio_idx;
+				mode &= ~mask;
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+					mode |= ME4600_DIO_CTRL_BIT_FUNCTION_0;
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+					mode |= ME4600_DIO_CTRL_BIT_FUNCTION_0;
+					mode |=
+					    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+					    instance->dio_idx;
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else if (single_config ==
+				   ME_SINGLE_CONFIG_DIO_BIT_PATTERN) {
+				mask =
+				    (ME4600_DIO_CTRL_BIT_MODE_0 |
+				     ME4600_DIO_CTRL_BIT_MODE_1) << (instance->
+								     dio_idx *
+								     2);
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+				    ME4600_DIO_CTRL_BIT_FUNCTION_1;
+				mask |=
+				    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+				    instance->dio_idx;
+				mode &= ~mask;
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |=
+					    (ME4600_DIO_CTRL_BIT_MODE_0 |
+					     ME4600_DIO_CTRL_BIT_MODE_1) <<
+					    (instance->dio_idx * 2);
+					mode |=
+					    ME4600_DIO_CTRL_BIT_FIFO_HIGH_0 <<
+					    instance->dio_idx;
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outl(mode, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, mode);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_dio_io_single_read(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int *value, int time_out, int flags)
+{
+	me4600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inl(instance->
+				ctrl_reg) & ((ME4600_DIO_CTRL_BIT_MODE_0 |
+					      ME4600_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+			if ((mode ==
+			     (ME4600_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value =
+				    inl(instance->port_reg) & (0x1 << channel);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inl(instance->
+				ctrl_reg) & ((ME4600_DIO_CTRL_BIT_MODE_0 |
+					      ME4600_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+			if ((mode ==
+			     (ME4600_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value = inl(instance->port_reg) & 0xFF;
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_dio_io_single_write(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int value, int time_out, int flags)
+{
+	me4600_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+	uint32_t byte;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inl(instance->
+				ctrl_reg) & ((ME4600_DIO_CTRL_BIT_MODE_0 |
+					      ME4600_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME4600_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				byte = inl(instance->port_reg) & 0xFF;
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outl(byte, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inl(instance->
+				ctrl_reg) & ((ME4600_DIO_CTRL_BIT_MODE_0 |
+					      ME4600_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME4600_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				outl(value, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_dio_query_number_channels(me_subdevice_t * subdevice,
+					    int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_dio_query_subdevice_type(me_subdevice_t * subdevice,
+					   int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_dio_query_subdevice_caps(me_subdevice_t * subdevice,
+					   int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_DIR_BYTE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me4600_dio_subdevice_t *me4600_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock)
+{
+	me4600_dio_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me4600_dio_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_dio_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save digital i/o index */
+	subdevice->dio_idx = dio_idx;
+
+	/* Save the subdevice index */
+	subdevice->ctrl_reg = reg_base + ME4600_DIO_CTRL_REG;
+	subdevice->port_reg = reg_base + ME4600_DIO_PORT_REG + (dio_idx * 4);
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_dio_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_dio_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_dio_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me4600_dio_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_dio_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_dio_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_dio_query_subdevice_caps;
+
+	return subdevice;
+}

+ 69 - 0
drivers/staging/meilhaus/me4600_dio.h

@@ -0,0 +1,69 @@
+/**
+ * @file me4600_dio.h
+ *
+ * @brief ME-4000 digital input/output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_DIO_H_
+#define _ME4600_DIO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me4600_dio_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+	unsigned int dio_idx;			/**< The index of the digital i/o on the device. */
+
+	/* Registers */
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me4600_dio_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-4000 digital input/ouput subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param dio_idx The index of the digital i/o port on the device.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me4600_dio_subdevice_t *me4600_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 63 - 0
drivers/staging/meilhaus/me4600_dio_reg.h

@@ -0,0 +1,63 @@
+/**
+ * @file me4600_dio_reg.h
+ *
+ * @brief ME-4000 digital input/output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_DIO_REG_H_
+#define _ME4600_DIO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME4600_DIO_PORT_0_REG				0xA0					/**< Port 0 register. */
+#define ME4600_DIO_PORT_1_REG				0xA4					/**< Port 1 register. */
+#define ME4600_DIO_PORT_2_REG				0xA8					/**< Port 2 register. */
+#define ME4600_DIO_PORT_3_REG				0xAC					/**< Port 3 register. */
+
+#define ME4600_DIO_DIR_REG					0xB0					/**< Direction register. */
+#define ME4600_DIO_PORT_REG					ME4600_DIO_PORT_0_REG	/**< Base for port's register. */
+
+#define ME4600_DIO_CTRL_REG					0xB8					/**< Control register. */
+/** Port A - DO */
+#define ME4600_DIO_CTRL_BIT_MODE_0			0x0001
+#define ME4600_DIO_CTRL_BIT_MODE_1			0x0002
+/** Port B - DI */
+#define ME4600_DIO_CTRL_BIT_MODE_2			0x0004
+#define ME4600_DIO_CTRL_BIT_MODE_3			0x0008
+/** Port C - DIO */
+#define ME4600_DIO_CTRL_BIT_MODE_4			0x0010
+#define ME4600_DIO_CTRL_BIT_MODE_5			0x0020
+/** Port D - DIO */
+#define ME4600_DIO_CTRL_BIT_MODE_6			0x0040
+#define ME4600_DIO_CTRL_BIT_MODE_7			0x0080
+
+#define ME4600_DIO_CTRL_BIT_FUNCTION_0		0x0100
+#define ME4600_DIO_CTRL_BIT_FUNCTION_1		0x0200
+
+#define ME4600_DIO_CTRL_BIT_FIFO_HIGH_0		0x0400
+#define ME4600_DIO_CTRL_BIT_FIFO_HIGH_1		0x0800
+#define ME4600_DIO_CTRL_BIT_FIFO_HIGH_2		0x1000
+#define ME4600_DIO_CTRL_BIT_FIFO_HIGH_3		0x2000
+
+#endif
+#endif

+ 433 - 0
drivers/staging/meilhaus/me4600_do.c

@@ -0,0 +1,433 @@
+/**
+ * @file me4600_do.c
+ *
+ * @brief ME-4000 digital output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me4600_dio_reg.h"
+#include "me4600_do.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me4600_do_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me4600_do_subdevice_t *instance;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	/* Set port to output mode */
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inl(instance->ctrl_reg);
+	mode &= ~ME4600_DIO_CTRL_BIT_MODE_1;	//0xFFFD
+	mode |= ME4600_DIO_CTRL_BIT_MODE_0;	//0x1
+	outl(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outl(0, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_do_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me4600_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+	int size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inl(instance->ctrl_reg);
+
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				mode &= ~(ME4600_DIO_CTRL_BIT_MODE_0 |
+					  ME4600_DIO_CTRL_BIT_MODE_1);
+				mode |= (ME4600_DIO_CTRL_BIT_MODE_0);
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_MUX32M) {
+				mode &= ~(ME4600_DIO_CTRL_BIT_MODE_0 |
+					  ME4600_DIO_CTRL_BIT_MODE_1 |
+					  ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+					  ME4600_DIO_CTRL_BIT_FUNCTION_1 |
+					  ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1 |
+						 ME4600_DIO_CTRL_BIT_FUNCTION_1);
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1 |
+						 ME4600_DIO_CTRL_BIT_FUNCTION_1
+						 |
+						 ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else if (single_config ==
+				   ME_SINGLE_CONFIG_DIO_DEMUX32) {
+				mode &=
+				    ~(ME4600_DIO_CTRL_BIT_MODE_0 |
+				      ME4600_DIO_CTRL_BIT_MODE_1 |
+				      ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+				      ME4600_DIO_CTRL_BIT_FUNCTION_1 |
+				      ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1 |
+						 ME4600_DIO_CTRL_BIT_FUNCTION_0);
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1 |
+						 ME4600_DIO_CTRL_BIT_FUNCTION_0
+						 |
+						 ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else if (single_config ==
+				   ME_SINGLE_CONFIG_DIO_BIT_PATTERN) {
+				mode &=
+				    ~(ME4600_DIO_CTRL_BIT_MODE_0 |
+				      ME4600_DIO_CTRL_BIT_MODE_1 |
+				      ME4600_DIO_CTRL_BIT_FUNCTION_0 |
+				      ME4600_DIO_CTRL_BIT_FUNCTION_1 |
+				      ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+
+				if (ref == ME_REF_DIO_FIFO_LOW) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1);
+				} else if (ref == ME_REF_DIO_FIFO_HIGH) {
+					mode |= (ME4600_DIO_CTRL_BIT_MODE_0 |
+						 ME4600_DIO_CTRL_BIT_MODE_1 |
+						 ME4600_DIO_CTRL_BIT_FIFO_HIGH_0);
+				} else {
+					PERROR
+					    ("Invalid port reference specified.\n");
+					err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+				}
+			} else {
+				PERROR("Invalid port direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outl(mode, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, mode);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_do_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me4600_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode =
+	    inl(instance->
+		ctrl_reg) & (ME4600_DIO_CTRL_BIT_MODE_0 |
+			     ME4600_DIO_CTRL_BIT_MODE_1);
+
+	if (mode == ME4600_DIO_CTRL_BIT_MODE_0) {
+		switch (flags) {
+		case ME_IO_SINGLE_TYPE_DIO_BIT:
+			if ((channel >= 0) && (channel < 8)) {
+				*value =
+				    inl(instance->port_reg) & (0x1 << channel);
+			} else {
+				PERROR("Invalid bit number specified.\n");
+				err = ME_ERRNO_INVALID_CHANNEL;
+			}
+			break;
+
+		case ME_IO_SINGLE_NO_FLAGS:
+		case ME_IO_SINGLE_TYPE_DIO_BYTE:
+			if (channel == 0) {
+				*value = inl(instance->port_reg) & 0xFF;
+			} else {
+				PERROR("Invalid byte number specified.\n");
+				err = ME_ERRNO_INVALID_CHANNEL;
+			}
+			break;
+
+		default:
+			PERROR("Invalid flags specified.\n");
+			err = ME_ERRNO_INVALID_FLAGS;
+		}
+	} else {
+		PERROR("Port not in output mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_do_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me4600_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t byte;
+	uint32_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode =
+	    inl(instance->
+		ctrl_reg) & (ME4600_DIO_CTRL_BIT_MODE_0 |
+			     ME4600_DIO_CTRL_BIT_MODE_1);
+
+	if (mode == ME4600_DIO_CTRL_BIT_MODE_0) {
+		switch (flags) {
+
+		case ME_IO_SINGLE_TYPE_DIO_BIT:
+			if ((channel >= 0) && (channel < 8)) {
+				byte = inl(instance->port_reg) & 0xFF;
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outl(byte, instance->port_reg);
+			} else {
+				PERROR("Invalid bit number specified.\n");
+				err = ME_ERRNO_INVALID_CHANNEL;
+			}
+			break;
+
+		case ME_IO_SINGLE_NO_FLAGS:
+		case ME_IO_SINGLE_TYPE_DIO_BYTE:
+			if (channel == 0) {
+				outl(value, instance->port_reg);
+			} else {
+				PERROR("Invalid byte number specified.\n");
+				err = ME_ERRNO_INVALID_CHANNEL;
+			}
+			break;
+
+		default:
+			PERROR("Invalid flags specified.\n");
+			err = ME_ERRNO_INVALID_FLAGS;
+		}
+	} else {
+		PERROR("Port not in output mode.\n");
+		err = ME_ERRNO_PREVIOUS_CONFIG;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_do_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_do_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_do_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = 0;
+	return ME_ERRNO_SUCCESS;
+}
+
+me4600_do_subdevice_t *me4600_do_constructor(uint32_t reg_base,
+					     spinlock_t * ctrl_reg_lock)
+{
+	me4600_do_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me4600_do_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_do_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the subdevice index */
+	subdevice->ctrl_reg = reg_base + ME4600_DIO_CTRL_REG;
+	subdevice->port_reg = reg_base + ME4600_DIO_PORT_0_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_do_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me4600_do_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me4600_do_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me4600_do_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_do_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_do_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_do_query_subdevice_caps;
+
+	return subdevice;
+}

+ 65 - 0
drivers/staging/meilhaus/me4600_do.h

@@ -0,0 +1,65 @@
+/**
+ * @file me4600_do.h
+ *
+ * @brief ME-4000 digital output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_DO_H_
+#define _ME4600_DO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me4600_do_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me4600_do_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-4000 digital output subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me4600_do_subdevice_t *me4600_do_constructor(uint32_t reg_base,
+					     spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 467 - 0
drivers/staging/meilhaus/me4600_ext_irq.c

@@ -0,0 +1,467 @@
+/**
+ * @file me4600_ext_irq.c
+ *
+ * @brief ME-4000 external interrupt subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/version.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "meids.h"
+#include "me4600_reg.h"
+#include "me4600_ai_reg.h"
+#include "me4600_ext_irq_reg.h"
+#include "me4600_ext_irq.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me4600_ext_irq_io_irq_start(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int irq_source,
+				       int irq_edge, int irq_arg, int flags)
+{
+	me4600_ext_irq_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags;
+	uint32_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags & ~ME_IO_IRQ_START_DIO_BIT) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((irq_edge != ME_IRQ_EDGE_RISING)
+	    && (irq_edge != ME_IRQ_EDGE_FALLING)
+	    && (irq_edge != ME_IRQ_EDGE_ANY)
+	    ) {
+		PERROR("Invalid irq edge specified.\n");
+		return ME_ERRNO_INVALID_IRQ_EDGE;
+	}
+
+	if (irq_source != ME_IRQ_SOURCE_DIO_LINE) {
+		PERROR("Invalid irq source specified.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	tmp = 0x0;		//inl(instance->ext_irq_config_reg);
+
+	if (irq_edge == ME_IRQ_EDGE_RISING) {
+		//tmp &= ~ME4600_EXT_IRQ_CONFIG_MASK;
+		//tmp |= ME4600_EXT_IRQ_CONFIG_MASK_RISING;
+	} else if (irq_edge == ME_IRQ_EDGE_FALLING) {
+		//tmp &= ~ME4600_EXT_IRQ_CONFIG_MASK;
+		//tmp |= ME4600_EXT_IRQ_CONFIG_MASK_FALLING;
+		tmp = ME4600_EXT_IRQ_CONFIG_MASK_FALLING;
+	} else if (irq_edge == ME_IRQ_EDGE_ANY) {
+		//tmp &= ~ME4600_EXT_IRQ_CONFIG_MASK;
+		//tmp |= ME4600_EXT_IRQ_CONFIG_MASK_ANY;
+		tmp = ME4600_EXT_IRQ_CONFIG_MASK_ANY;
+	}
+
+	outl(tmp, instance->ext_irq_config_reg);
+	PDEBUG_REG("ext_irq_config_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->ext_irq_config_reg - instance->reg_base, tmp);
+
+	spin_lock_irqsave(instance->ctrl_reg_lock, cpu_flags);
+	tmp = inl(instance->ctrl_reg);
+	tmp &= ~(ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET);
+	tmp |= ME4600_AI_CTRL_BIT_EX_IRQ;
+	outl(tmp, instance->ctrl_reg);
+	spin_unlock_irqrestore(instance->ctrl_reg_lock, cpu_flags);
+	instance->rised = 0;
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ext_irq_io_irq_wait(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int *irq_count,
+				      int *value, int time_out, int flags)
+{
+	me4600_ext_irq_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     (instance->rised !=
+							      0), t);
+
+			if (t == 0) {
+				PERROR
+				    ("Wait on external interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on external interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	instance->rised = 0;
+	*irq_count = instance->count;
+	*value = instance->value;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ext_irq_io_irq_stop(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel, int flags)
+{
+	me4600_ext_irq_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags;
+	uint32_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->ctrl_reg_lock);
+	tmp = inl(instance->ctrl_reg);
+	tmp &= ~(ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_regv outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->ctrl_reg_lock);
+	instance->rised = -1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me4600_ext_irq_io_reset_subdevice(me_subdevice_t * subdevice,
+					     struct file *filep, int flags)
+{
+	me4600_ext_irq_subdevice_t *instance;
+	unsigned long cpu_flags;
+	uint32_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me4600_ext_irq_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->ctrl_reg_lock);
+	tmp = inl(instance->ctrl_reg);
+	tmp &= ~(ME4600_AI_CTRL_BIT_EX_IRQ | ME4600_AI_CTRL_BIT_EX_IRQ_RESET);
+	outl(tmp, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_regv outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->ctrl_reg_lock);
+	instance->rised = -1;
+	instance->count = 0;
+	outl(ME4600_EXT_IRQ_CONFIG_MASK_ANY, instance->ext_irq_config_reg);
+	PDEBUG_REG("ext_irq_config_reg outl(0x%lX+0x%lX)=0x%x\n",
+		   instance->reg_base,
+		   instance->ext_irq_config_reg - instance->reg_base,
+		   ME4600_EXT_IRQ_CONFIG_MASK_ANY);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static void me4600_ext_irq_destructor(struct me_subdevice *subdevice)
+{
+	me4600_ext_irq_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+	instance = (me4600_ext_irq_subdevice_t *) subdevice;
+	me_subdevice_deinit(&instance->base);
+	free_irq(instance->irq, instance);
+	kfree(instance);
+}
+
+static int me4600_ext_irq_query_number_channels(me_subdevice_t * subdevice,
+						int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 1;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ext_irq_query_subdevice_type(me_subdevice_t * subdevice,
+					       int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_EXT_IRQ;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ext_irq_query_subdevice_caps(me_subdevice_t * subdevice,
+					       int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps =
+	    ME_CAPS_EXT_IRQ_EDGE_RISING | ME_CAPS_EXT_IRQ_EDGE_FALLING |
+	    ME_CAPS_EXT_IRQ_EDGE_ANY;
+	return ME_ERRNO_SUCCESS;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me4600_ext_irq_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me4600_ext_irq_isr(int irq, void *dev_id,
+				      struct pt_regs *regs)
+#endif
+{
+	me4600_ext_irq_subdevice_t *instance;
+	uint32_t ctrl;
+	uint32_t irq_status;
+
+	instance = (me4600_ext_irq_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inl(instance->irq_status_reg);
+	if (!(irq_status & ME4600_IRQ_STATUS_BIT_EX)) {
+		PINFO("%ld Shared interrupt. %s(): irq_status_reg=0x%04X\n",
+		      jiffies, __FUNCTION__, irq_status);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	spin_lock(&instance->subdevice_lock);
+	instance->rised = 1;
+	instance->value = inl(instance->ext_irq_value_reg);
+	instance->count++;
+
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inl(instance->ctrl_reg);
+	ctrl |= ME4600_AI_CTRL_BIT_EX_IRQ_RESET;
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	ctrl &= ~ME4600_AI_CTRL_BIT_EX_IRQ_RESET;
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	spin_unlock(&instance->subdevice_lock);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+me4600_ext_irq_subdevice_t *me4600_ext_irq_constructor(uint32_t reg_base,
+						       int irq,
+						       spinlock_t *
+						       ctrl_reg_lock)
+{
+	me4600_ext_irq_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me4600_ext_irq_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me4600_ext_irq_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Register interrupt */
+	subdevice->irq = irq;
+
+	if (request_irq(subdevice->irq, me4600_ext_irq_isr,
+#ifdef IRQF_DISABLED
+			IRQF_DISABLED | IRQF_SHARED,
+#else
+			SA_INTERRUPT | SA_SHIRQ,
+#endif
+			ME4600_NAME, subdevice)) {
+		PERROR("Cannot register interrupt.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", subdevice->irq);
+
+	/* Initialize registers */
+	subdevice->irq_status_reg = reg_base + ME4600_IRQ_STATUS_REG;
+	subdevice->ctrl_reg = reg_base + ME4600_AI_CTRL_REG;
+	subdevice->ext_irq_config_reg = reg_base + ME4600_EXT_IRQ_CONFIG_REG;
+	subdevice->ext_irq_value_reg = reg_base + ME4600_EXT_IRQ_VALUE_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Override base class methods. */
+	subdevice->base.me_subdevice_destructor = me4600_ext_irq_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me4600_ext_irq_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_irq_start = me4600_ext_irq_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me4600_ext_irq_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me4600_ext_irq_io_irq_stop;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me4600_ext_irq_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me4600_ext_irq_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me4600_ext_irq_query_subdevice_caps;
+
+	subdevice->rised = 0;
+	subdevice->count = 0;
+
+	return subdevice;
+}

+ 78 - 0
drivers/staging/meilhaus/me4600_ext_irq.h

@@ -0,0 +1,78 @@
+/**
+ * @file me4600_ext_irq.h
+ *
+ * @brief Meilhaus ME-4000 external interrupt subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_EXT_IRQ_H_
+#define _ME4600_EXT_IRQ_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The subdevice class.
+ */
+typedef struct me4600_ext_irq_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+
+	wait_queue_head_t wait_queue;
+
+	int irq;
+
+	int rised;
+	int value;
+	int count;
+
+	unsigned long ctrl_reg;
+	unsigned long irq_status_reg;
+	unsigned long ext_irq_config_reg;
+	unsigned long ext_irq_value_reg;
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me4600_ext_irq_subdevice_t;
+
+/**
+ * @brief The constructor to generate a external interrupt subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param irq The interrupt number assigned by the PCI BIOS.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the control register from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me4600_ext_irq_subdevice_t *me4600_ext_irq_constructor(uint32_t reg_base,
+						       int irq,
+						       spinlock_t *
+						       ctrl_reg_lock);
+
+#endif
+#endif

+ 41 - 0
drivers/staging/meilhaus/me4600_ext_irq_reg.h

@@ -0,0 +1,41 @@
+/**
+ * @file me4600_ext_irq_reg.h
+ *
+ * @brief ME-4000 external interrupt subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_EXT_IRQ_REG_H_
+#define _ME4600_EXT_IRQ_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME4600_EXT_IRQ_CONFIG_REG		0xCC	// R/_
+#define ME4600_EXT_IRQ_VALUE_REG		0xD0	// R/_
+
+#define ME4600_EXT_IRQ_CONFIG_MASK_RISING	0x0
+#define ME4600_EXT_IRQ_CONFIG_MASK_FALLING	0x1
+#define ME4600_EXT_IRQ_CONFIG_MASK_ANY		0x3
+#define ME4600_EXT_IRQ_CONFIG_MASK		0x3
+
+#endif
+#endif

+ 46 - 0
drivers/staging/meilhaus/me4600_reg.h

@@ -0,0 +1,46 @@
+/**
+ * @file me4600_reg.h
+ *
+ * @brief ME-4000 register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME4600_REG_H_
+#define _ME4600_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME4600_IRQ_STATUS_REG			0x9C	// R/_
+
+#define ME4600_IRQ_STATUS_BIT_EX		0x01
+#define ME4600_IRQ_STATUS_BIT_LE		0x02
+#define ME4600_IRQ_STATUS_BIT_AI_HF		0x04
+#define ME4600_IRQ_STATUS_BIT_AO_0_HF	0x08
+#define ME4600_IRQ_STATUS_BIT_AO_1_HF	0x10
+#define ME4600_IRQ_STATUS_BIT_AO_2_HF	0x20
+#define ME4600_IRQ_STATUS_BIT_AO_3_HF	0x40
+#define ME4600_IRQ_STATUS_BIT_SC		0x80
+
+#define ME4600_IRQ_STATUS_BIT_AO_HF		ME4600_IRQ_STATUS_BIT_AO_0_HF
+
+#endif
+#endif

+ 3739 - 0
drivers/staging/meilhaus/me6000_ao.c

@@ -0,0 +1,3739 @@
+/**
+ * @file me6000_ao.c
+ *
+ * @brief ME-6000 analog output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/* Includes
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <linux/workqueue.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "meids.h"
+#include "me6000_reg.h"
+#include "me6000_ao_reg.h"
+#include "me6000_ao.h"
+
+/* Defines
+ */
+
+static int me6000_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range);
+
+static int me6000_ao_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count);
+
+static int me6000_ao_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata);
+
+static int me6000_ao_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks);
+
+static int me6000_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number);
+
+static int me6000_ao_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype);
+
+static int me6000_ao_query_subdevice_caps(me_subdevice_t * subdevice,
+					  int *caps);
+
+static int me6000_ao_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count);
+
+/** Remove subdevice. */
+static void me6000_ao_destructor(struct me_subdevice *subdevice);
+
+/** Reset subdevice. Stop all actions. Reset registry. Disable FIFO. Set output to 0V and status to 'none'. */
+static int me6000_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags);
+
+/** Set output as single */
+static int me6000_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags);
+
+/** Pass to user actual value of output. */
+static int me6000_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags);
+
+/** Write to output requed value. */
+static int me6000_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags);
+
+/** Set output as streamed device. */
+static int me6000_ao_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags);
+
+/** Wait for / Check empty space in buffer. */
+static int me6000_ao_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags);
+
+/** Start streaming. */
+static int me6000_ao_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags);
+
+/** Check actual state. / Wait for end. */
+static int me6000_ao_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags);
+
+/** Stop streaming. */
+static int me6000_ao_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags);
+
+/** Write datas to buffor. */
+static int me6000_ao_io_stream_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int write_mode,
+				     int *values, int *count, int flags);
+
+/** Interrupt handler. Copy from buffer to FIFO. */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me6000_ao_isr(int irq, void *dev_id);
+#else
+static irqreturn_t me6000_ao_isr(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+/** Copy data from circular buffer to fifo (fast) in wraparound mode. */
+int inline ao_write_data_wraparound(me6000_ao_subdevice_t * instance, int count,
+				    int start_pos);
+
+/** Copy data from circular buffer to fifo (fast).*/
+int inline ao_write_data(me6000_ao_subdevice_t * instance, int count,
+			 int start_pos);
+
+/** Copy data from circular buffer to fifo (slow).*/
+int inline ao_write_data_pooling(me6000_ao_subdevice_t * instance, int count,
+				 int start_pos);
+
+/** Copy data from user space to circular buffer. */
+int inline ao_get_data_from_user(me6000_ao_subdevice_t * instance, int count,
+				 int *user_values);
+
+/** Stop presentation. Preserve FIFOs. */
+int inline ao_stop_immediately(me6000_ao_subdevice_t * instance);
+
+/** Function for checking timeout in non-blocking mode. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+static void me6000_ao_work_control_task(void *subdevice);
+#else
+static void me6000_ao_work_control_task(struct work_struct *work);
+#endif
+
+/* Functions
+ */
+
+static int me6000_ao_io_reset_subdevice(me_subdevice_t * subdevice,
+					struct file *filep, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t tmp;
+	uint32_t ctrl;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	instance->status = ao_status_none;
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+	instance->timeout.delay = 0;
+	instance->timeout.start_time = jiffies;
+
+	//Stop state machine.
+	err = ao_stop_immediately(instance);
+
+	//Remove from synchronous start.
+	spin_lock(instance->preload_reg_lock);
+	tmp = inl(instance->preload_reg);
+	tmp &=
+	    ~((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+	outl(tmp, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, tmp);
+	*instance->preload_flags &=
+	    ~((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+
+	//Reset triggering flag
+	*instance->triggering_flags &= ~(0x1 << instance->ao_idx);
+	spin_unlock(instance->preload_reg_lock);
+
+	if (instance->fifo) {
+		//Set single mode, dissable FIFO, dissable external trigger, block interrupt.
+		ctrl = ME6000_AO_MODE_SINGLE;
+
+		//Block ISM.
+		ctrl |=
+		    (ME6000_AO_CTRL_BIT_STOP |
+		     ME6000_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		//Set speed
+		outl(ME6000_AO_MIN_CHAN_TICKS - 1, instance->timer_reg);
+		//Reset interrupt latch
+		inl(instance->irq_reset_reg);
+	}
+
+	instance->hardware_stop_delay = HZ / 10;	//100ms
+
+	//Set output to 0V
+	outl(0x8000, instance->single_reg);
+	PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->single_reg - instance->reg_base, 0x8000);
+
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+	instance->preloaded_count = 0;
+	instance->data_count = 0;
+	instance->single_value = 0x8000;
+	instance->single_value_in_fifo = 0x8000;
+
+	//Set status to signal that device is unconfigured.
+	instance->status = ao_status_none;
+	//Signal reset if user is on wait.
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t ctrl;
+	uint32_t sync;
+	unsigned long cpu_flags;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. ID=%d\n", instance->ao_idx);
+
+	// Checking parameters
+	if (flags) {
+		PERROR
+		    ("Invalid flag specified. Must be ME_IO_SINGLE_CONFIG_NO_FLAGS.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (instance->fifo) {	//Stream hardware (with or without fifo)
+		if ((trig_edge == ME_TRIG_TYPE_SW)
+		    && (trig_edge != ME_TRIG_EDGE_NONE)) {
+			PERROR
+			    ("Invalid trigger edge. Software trigger has not edge.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+
+		if (trig_type == ME_TRIG_TYPE_EXT_DIGITAL) {
+			switch (trig_edge) {
+			case ME_TRIG_EDGE_ANY:
+			case ME_TRIG_EDGE_RISING:
+			case ME_TRIG_EDGE_FALLING:
+				break;
+
+			default:
+				PERROR("Invalid trigger edge.\n");
+				return ME_ERRNO_INVALID_TRIG_EDGE;
+			}
+		}
+
+		if ((trig_type != ME_TRIG_TYPE_SW)
+		    && (trig_type != ME_TRIG_TYPE_EXT_DIGITAL)) {
+			PERROR
+			    ("Invalid trigger type. Trigger must be software or digital.\n");
+			return ME_ERRNO_INVALID_TRIG_TYPE;
+		}
+	} else {		//Single
+		if (trig_edge != ME_TRIG_EDGE_NONE) {
+			PERROR
+			    ("Invalid trigger edge. Single output trigger hasn't own edge.\n");
+			return ME_ERRNO_INVALID_TRIG_EDGE;
+		}
+
+		if (trig_type != ME_TRIG_TYPE_SW) {
+			PERROR
+			    ("Invalid trigger type. Trigger must be software.\n");
+			return ME_ERRNO_INVALID_TRIG_TYPE;
+		}
+
+	}
+
+	if ((trig_chan != ME_TRIG_CHAN_DEFAULT)
+	    && (trig_chan != ME_TRIG_CHAN_SYNCHRONOUS)) {
+		PERROR("Invalid trigger channel specified.\n");
+		return ME_ERRNO_INVALID_TRIG_CHAN;
+	}
+/*
+	if ((trig_type == ME_TRIG_TYPE_EXT_DIGITAL) && (trig_chan != ME_TRIG_CHAN_SYNCHRONOUS))
+	{
+		PERROR("Invalid trigger channel specified. Must be synchronous when digital is choose.\n");
+		return ME_ERRNO_INVALID_TRIG_CHAN;
+	}
+*/
+	if (ref != ME_REF_AO_GROUND) {
+		PERROR
+		    ("Invalid reference. Analog outputs have to have got REF_AO_GROUND.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if (single_config != 0) {
+		PERROR
+		    ("Invalid single config specified. Only one range for anlog outputs is available.\n");
+		return ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+
+	if (channel != 0) {
+		PERROR
+		    ("Invalid channel number specified. Analog output have only one channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Subdevice running in stream mode!
+	if ((instance->status >= ao_status_stream_run_wait)
+	    && (instance->status < ao_status_stream_end)) {
+		PERROR("Subdevice is busy.\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+/// @note For single all calls (config and write) are erasing previous state!
+
+	instance->status = ao_status_none;
+
+	// Correct single mirrors
+	instance->single_value_in_fifo = instance->single_value;
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	if (instance->fifo) {	// Set control register.
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		// Set stop bit. Stop streaming mode (If running.).
+		ctrl = inl(instance->ctrl_reg);
+		//Reset all bits.
+		ctrl =
+		    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP | ME6000_AO_CTRL_BIT_STOP;
+		if (trig_type == ME_TRIG_TYPE_EXT_DIGITAL) {
+			PINFO("External digital trigger.\n");
+
+			if (trig_edge == ME_TRIG_EDGE_ANY) {
+//                                      ctrl |= ME6000_AO_CTRL_BIT_EX_TRIG_EDGE | ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+				instance->ctrl_trg =
+				    ME6000_AO_CTRL_BIT_EX_TRIG_EDGE |
+				    ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			} else if (trig_edge == ME_TRIG_EDGE_FALLING) {
+//                                      ctrl |= ME6000_AO_CTRL_BIT_EX_TRIG_EDGE;
+				instance->ctrl_trg =
+				    ME6000_AO_CTRL_BIT_EX_TRIG_EDGE;
+			} else if (trig_edge == ME_TRIG_EDGE_RISING) {
+				instance->ctrl_trg = 0x0;
+			}
+		} else if (trig_type == ME_TRIG_TYPE_SW) {
+			PDEBUG("SOFTWARE TRIGGER\n");
+			instance->ctrl_trg = 0x0;
+		}
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	} else {
+		PDEBUG("SOFTWARE TRIGGER\n");
+	}
+
+	// Set preload/synchronization register.
+	spin_lock(instance->preload_reg_lock);
+
+	if (trig_type == ME_TRIG_TYPE_SW) {
+		*instance->preload_flags &=
+		    ~(ME6000_AO_SYNC_EXT_TRIG << instance->ao_idx);
+	} else			//if (trig_type == ME_TRIG_TYPE_EXT_DIGITAL)
+	{
+		*instance->preload_flags |=
+		    ME6000_AO_SYNC_EXT_TRIG << instance->ao_idx;
+	}
+
+	if (trig_chan == ME_TRIG_CHAN_DEFAULT) {
+		*instance->preload_flags &=
+		    ~(ME6000_AO_SYNC_HOLD << instance->ao_idx);
+	} else			//if (trig_chan == ME_TRIG_CHAN_SYNCHRONOUS)
+	{
+		*instance->preload_flags |=
+		    ME6000_AO_SYNC_HOLD << instance->ao_idx;
+	}
+
+	//Reset hardware register
+	sync = inl(instance->preload_reg);
+	PDEBUG_REG("preload_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync);
+	sync &= ~(ME6000_AO_SYNC_EXT_TRIG << instance->ao_idx);
+	sync |= ME6000_AO_SYNC_HOLD << instance->ao_idx;
+
+	//Output configured in default mode (safe one)
+	outl(sync, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync);
+	spin_unlock(instance->preload_reg_lock);
+
+	instance->status = ao_status_single_configured;
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	unsigned long j;
+	unsigned long delay = 0;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags & ~ME_IO_SINGLE_NONBLOCKING) {
+		PERROR("Invalid flag specified. %d\n", flags);
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((instance->status >= ao_status_stream_configured)
+	    && (instance->status <= ao_status_stream_end)) {
+		PERROR("Subdevice not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+	if ((!flags) && (instance->status == ao_status_single_run_wait)) {	//Blocking mode. Wait for trigger.
+		if (time_out) {
+			delay = (time_out * HZ) / 1000;
+			if (delay == 0)
+				delay = 1;
+		}
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Events are signaled when status change. This procedure has own timeout.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_single_run_wait),
+						 (delay) ? delay : LONG_MAX);
+
+		if (instance->status == ao_status_none) {
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+			PDEBUG("Timeout reached.\n");
+			err = ME_ERRNO_TIMEOUT;
+		}
+
+		*value =
+		    (!err) ? instance->single_value_in_fifo : instance->
+		    single_value;
+	} else {		//Non-blocking mode
+		//Read value
+		*value = instance->single_value;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags;
+	unsigned long j;
+	unsigned long delay = 0;
+
+	uint32_t sync_mask;
+	uint32_t mode;
+
+	uint32_t tmp;
+
+/// Workaround for mix-mode - begin
+	uint32_t ctrl = 0x0;
+	uint32_t status;
+/// Workaround for mix-mode - end
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags &
+	    ~(ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS |
+	      ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((instance->status == ao_status_none)
+	    || (instance->status > ao_status_single_end)) {
+		PERROR("Subdevice not configured to work in single mode!\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	if (channel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (value & ~ME6000_AO_MAX_DATA) {
+		PERROR("Invalid value provided.\n");
+		return ME_ERRNO_VALUE_OUT_OF_RANGE;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+/// @note For single all calls (config and write) are erasing previous state!
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	// Correct single mirrors
+	instance->single_value_in_fifo = instance->single_value;
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+
+		if (delay == 0)
+			delay = 1;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+
+	instance->single_value_in_fifo = value;
+
+	if (instance->fifo) {
+		ctrl = inl(instance->ctrl_reg);
+	}
+
+	if (instance->fifo & ME6000_AO_HAS_FIFO) {	/// Workaround for mix-mode - begin
+		//Set speed
+		outl(ME6000_AO_MIN_CHAN_TICKS - 1, instance->timer_reg);
+		PDEBUG_REG("timer_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->timer_reg - instance->reg_base,
+			   (int)ME6000_AO_MIN_CHAN_TICKS);
+		instance->hardware_stop_delay = HZ / 10;	//100ms
+
+		status = inl(instance->status_reg);
+
+		//Set the continous mode.
+		ctrl &= ~ME6000_AO_CTRL_MODE_MASK;
+		ctrl |= ME6000_AO_MODE_CONTINUOUS;
+
+		//Prepare FIFO
+		if (!(ctrl & ME6000_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO wasn't enabeled. Do it.
+			PINFO("Enableing FIFO.\n");
+			ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			ctrl |= ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+		} else {	//Check if FIFO is empty
+			if (status & ME6000_AO_STATUS_BIT_EF) {	//FIFO not empty
+				PINFO("Reseting FIFO.\n");
+				ctrl &=
+				    ~(ME6000_AO_CTRL_BIT_ENABLE_FIFO |
+				      ME6000_AO_CTRL_BIT_ENABLE_IRQ);
+				outl(ctrl, instance->ctrl_reg);
+				PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->ctrl_reg -
+					   instance->reg_base, ctrl);
+
+				ctrl |= ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+			} else {	//FIFO empty, only interrupt needs to be disabled!
+				ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			}
+		}
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		//Reset interrupt latch
+		inl(instance->irq_reset_reg);
+
+		//Write output - 1 value to FIFO
+		if (instance->ao_idx & 0x1) {
+			outl(value <<= 16, instance->fifo_reg);
+			PDEBUG_REG("fifo_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->fifo_reg - instance->reg_base,
+				   value <<= 16);
+		} else {
+			outl(value, instance->fifo_reg);
+			PDEBUG_REG("fifo_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->fifo_reg - instance->reg_base,
+				   value);
+		}
+		/// Workaround for mix-mode - end
+	} else {		//No FIFO - always in single mode
+		//Write value
+		PDEBUG("Write value\n");
+		outl(value, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, value);
+	}
+
+	mode = *instance->preload_flags >> instance->ao_idx;
+	mode &= (ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG);
+
+	PINFO("Triggering mode: 0x%08x\n", mode);
+
+	spin_lock(instance->preload_reg_lock);
+	sync_mask = inl(instance->preload_reg);
+	PDEBUG_REG("preload_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, sync_mask);
+	switch (mode) {
+	case 0:		//0x00000000: Individual software
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if (instance->fifo & ME6000_AO_HAS_FIFO) {	// FIFO - Continous mode
+			ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+			if ((sync_mask & ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != 0x0) {	//Now we can set correct mode.
+				sync_mask &=
+				    ~((ME6000_AO_SYNC_EXT_TRIG |
+				       ME6000_AO_SYNC_HOLD) << instance->
+				      ao_idx);
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		} else {	// No FIFO - Single mode: In this case resetting 'ME6000_AO_SYNC_HOLD' will trigger output.
+			if ((sync_mask & ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != ME6000_AO_SYNC_HOLD) {	//Now we can set correct mode. This is exception. It is set to synchronous and triggered later.
+				sync_mask &=
+				    ~(ME6000_AO_SYNC_EXT_TRIG << instance->
+				      ao_idx);
+				sync_mask |=
+				    ME6000_AO_SYNC_HOLD << instance->ao_idx;
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		}
+		instance->single_value = value;
+		break;
+
+	case ME6000_AO_SYNC_EXT_TRIG:	//0x00010000: Individual hardware
+		PDEBUG("DIGITAL TRIGGER\n");
+		ctrl |= ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if (instance->fifo & ME6000_AO_HAS_FIFO) {	// FIFO - Continous mode
+			if ((sync_mask & ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->ao_idx)) != 0x0) {	//Now we can set correct mode.
+				sync_mask &=
+				    ~((ME6000_AO_SYNC_EXT_TRIG |
+				       ME6000_AO_SYNC_HOLD) << instance->
+				      ao_idx);
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		} else {	// No FIFO - Single mode
+			if ((sync_mask &
+			     ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) <<
+			      instance->ao_idx)) != ME6000_AO_SYNC_HOLD) {
+				//Now we can set correct mode
+				sync_mask &=
+				    ~(ME6000_AO_SYNC_EXT_TRIG << instance->
+				      ao_idx);
+				sync_mask |=
+				    ME6000_AO_SYNC_HOLD << instance->ao_idx;
+
+				outl(sync_mask, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     sync_mask);
+			}
+		}
+		break;
+
+	case ME6000_AO_SYNC_HOLD:	//0x00000001: Synchronous software
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if ((sync_mask &
+		     ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) <<
+		      instance->ao_idx)) !=
+		    (ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG)) {
+			//Now we can set correct mode
+			sync_mask |=
+			    ME6000_AO_SYNC_EXT_TRIG << instance->ao_idx;
+			sync_mask |= ME6000_AO_SYNC_HOLD << instance->ao_idx;
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		}
+		//Set triggering flag
+		*instance->triggering_flags |= 0x1 << instance->ao_idx;
+		break;
+
+	case (ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG):	//0x00010001: Synchronous hardware
+		PDEBUG("DIGITAL TRIGGER\n");
+		ctrl |= ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+
+		if ((sync_mask &
+		     ((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) <<
+		      instance->ao_idx)) !=
+		    (ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG)) {
+			//Now we can set correct mode
+			sync_mask |=
+			    (ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) <<
+			    instance->ao_idx;
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+		}
+		//Set triggering flag
+		*instance->triggering_flags |= 0x1 << instance->ao_idx;
+		break;
+	}
+//              spin_unlock(instance->preload_reg_lock);        // Moved down.
+
+	if (instance->fifo) {	//Activate ISM (remove 'stop' bits)
+		ctrl &=
+		    ~(ME6000_AO_CTRL_BIT_EX_TRIG_EDGE |
+		      ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH);
+		ctrl |= instance->ctrl_trg;
+		ctrl &=
+		    ~(ME6000_AO_CTRL_BIT_STOP |
+		      ME6000_AO_CTRL_BIT_IMMEDIATE_STOP);
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+/// @note When flag 'ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS' is set than output is triggered. ALWAYS!
+
+	PINFO("<%s> start mode= 0x%08x %s\n", __FUNCTION__, mode,
+	      (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) ? "SYNCHRONOUS" :
+	      "");
+	if (instance->fifo & ME6000_AO_HAS_FIFO) {	// FIFO - Continous mode
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Trigger outputs
+			//Add channel to start list
+			outl(sync_mask |
+			     (ME6000_AO_SYNC_HOLD << instance->ao_idx),
+			     instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask | (ME6000_AO_SYNC_HOLD <<
+						instance->ao_idx));
+
+			//Fire
+			PINFO
+			    ("Fired all software synchronous outputs by software trigger.\n");
+			outl(0x8000, instance->single_reg);
+			PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->single_reg - instance->reg_base,
+				   0x8000);
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+
+		} else if (!mode) {	//Trigger outputs
+/*			//Remove channel from start list
+			outl(sync_mask & ~(ME6000_AO_SYNC_HOLD << instance->ao_idx), instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, sync_mask & ~(ME6000_AO_SYNC_HOLD << instance->ao_idx));
+*/
+			//Fire
+			PINFO("Software trigger.\n");
+			outl(0x8000, instance->single_reg);
+			PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->single_reg - instance->reg_base,
+				   0x8000);
+
+/*			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, sync_mask);
+*/
+		}
+/// @note This is mix-mode case. For now I do not have possibility to trigger first 4 channels (continous mode) and other (single) ones at once.
+/// @note Because triggering is not working it can not be add to synchronous list. First 4 channels don't need this information, anyway.
+		*instance->triggering_flags &= 0xFFFFFFF0;
+	} else {		// No FIFO - Single mode
+		if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Fired all software synchronous outputs.
+			tmp = ~(*instance->preload_flags | 0xFFFF0000);
+			PINFO
+			    ("Fired all software synchronous outputs. mask:0x%08x\n",
+			     tmp);
+			tmp |= sync_mask & 0xFFFF0000;
+			// Add this channel to list
+			tmp &= ~(ME6000_AO_SYNC_HOLD << instance->ao_idx);
+
+			//Fire
+			PINFO("Software trigger.\n");
+			outl(tmp, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   tmp);
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+
+			//Set all as triggered.
+			*instance->triggering_flags = 0x0;
+		} else if (!mode) {	// Add this channel to list
+			outl(sync_mask &
+			     ~(ME6000_AO_SYNC_HOLD << instance->ao_idx),
+			     instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask & ~(ME6000_AO_SYNC_HOLD <<
+						 instance->ao_idx));
+
+			//Fire
+			PINFO("Software trigger.\n");
+
+			//Restore save settings
+			outl(sync_mask, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   sync_mask);
+
+			//Set all as triggered.
+			*instance->triggering_flags = 0x0;
+		}
+
+	}
+	spin_unlock(instance->preload_reg_lock);
+
+	instance->status = ao_status_single_run_wait;
+
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = jiffies;
+	instance->ao_control_task_flag = 1;
+	queue_delayed_work(instance->me6000_workqueue,
+			   &instance->ao_control_task, 1);
+
+	if (!(flags & ME_IO_SINGLE_TYPE_WRITE_NONBLOCKING)) {
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_single_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if (instance->status != ao_status_single_end) {
+			PDEBUG("Single canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->ao_control_task_flag = 0;
+			cancel_delayed_work(&instance->ao_control_task);
+			ao_stop_immediately(instance);
+			instance->status = ao_status_none;
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - j) >= delay)) {
+			if (instance->status == ao_status_single_end) {
+				PDEBUG("Timeout reached.\n");
+			} else if ((jiffies - j) > delay) {
+				PERROR
+				    ("Timeout reached. Not handled by control task!\n");
+				ao_stop_immediately(instance);
+			} else {
+				PERROR
+				    ("Timeout reached. Signal come but status is strange: %d\n",
+				     instance->status);
+				ao_stop_immediately(instance);
+			}
+
+			instance->ao_control_task_flag = 0;
+			cancel_delayed_work(&instance->ao_control_task);
+			instance->status = ao_status_single_end;
+			err = ME_ERRNO_TIMEOUT;
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_stream_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      meIOStreamConfig_t * config_list,
+				      int count,
+				      meIOStreamTrigger_t * trigger,
+				      int fifo_irq_threshold, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t ctrl;
+	unsigned long cpu_flags;
+	uint64_t conv_ticks;
+	unsigned int conv_start_ticks_low = trigger->iConvStartTicksLow;
+	unsigned int conv_start_ticks_high = trigger->iConvStartTicksHigh;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	conv_ticks =
+	    (uint64_t) conv_start_ticks_low +
+	    ((uint64_t) conv_start_ticks_high << 32);
+
+	if (flags &
+	    ~(ME_IO_STREAM_CONFIG_HARDWARE_ONLY |
+	      ME_IO_STREAM_CONFIG_WRAPAROUND)) {
+		PERROR("Invalid flags.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (flags & ME_IO_STREAM_CONFIG_HARDWARE_ONLY) {
+		if (!flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			PERROR
+			    ("Hardware ME_IO_STREAM_CONFIG_HARDWARE_ONLY has to be with ME_IO_STREAM_CONFIG_WRAPAROUND.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if ((trigger->iAcqStopTrigType != ME_TRIG_TYPE_NONE)
+		    || (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE)) {
+			PERROR
+			    ("Hardware wraparound mode must be in infinite mode.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+	}
+
+	if (count != 1) {
+		PERROR("Only 1 entry in config list acceptable.\n");
+		return ME_ERRNO_INVALID_CONFIG_LIST_COUNT;
+	}
+
+	if (config_list[0].iChannel != 0) {
+		PERROR("Invalid channel number specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (config_list[0].iStreamConfig != 0) {
+		PERROR("Only one range available.\n");
+		return ME_ERRNO_INVALID_STREAM_CONFIG;
+	}
+
+	if (config_list[0].iRef != ME_REF_AO_GROUND) {
+		PERROR("Output is referenced to ground.\n");
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	if ((trigger->iAcqStartTicksLow != 0)
+	    || (trigger->iAcqStartTicksHigh != 0)) {
+		PERROR
+		    ("Invalid acquisition start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_ARG;
+	}
+
+	if (config_list[0].iFlags) {
+		PERROR("Invalid config list flag.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((trigger->iAcqStartTrigType != ME_TRIG_TYPE_SW)
+	    && (trigger->iAcqStartTrigType != ME_TRIG_TYPE_EXT_DIGITAL)) {
+		PERROR("Invalid acquisition start trigger type specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_TRIG_TYPE;
+	}
+
+	if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_EXT_DIGITAL) {
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_RISING:
+		case ME_TRIG_EDGE_FALLING:
+		case ME_TRIG_EDGE_ANY:
+			break;
+
+		default:
+			PERROR
+			    ("Invalid acquisition start trigger edge specified.\n");
+			return ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+		}
+	}
+
+	if ((trigger->iAcqStartTrigType == ME_TRIG_TYPE_SW)
+	    && (trigger->iAcqStartTrigEdge != ME_TRIG_TYPE_NONE)) {
+		PERROR("Invalid acquisition start trigger edge specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_TRIG_EDGE;
+	}
+
+	if (trigger->iScanStartTrigType != ME_TRIG_TYPE_FOLLOW) {
+		PERROR("Invalid scan start trigger type specified.\n");
+		return ME_ERRNO_INVALID_SCAN_START_TRIG_TYPE;
+	}
+
+	if (trigger->iConvStartTrigType != ME_TRIG_TYPE_TIMER) {
+		PERROR("Invalid conv start trigger type specified.\n");
+		return ME_ERRNO_INVALID_CONV_START_TRIG_TYPE;
+	}
+
+	if ((conv_ticks < ME6000_AO_MIN_CHAN_TICKS)
+	    || (conv_ticks > ME6000_AO_MAX_CHAN_TICKS)) {
+		PERROR("Invalid conv start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_CONV_START_ARG;
+	}
+
+	if (trigger->iAcqStartTicksLow || trigger->iAcqStartTicksHigh) {
+		PERROR("Invalid acq start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_ARG;
+	}
+
+	if (trigger->iScanStartTicksLow || trigger->iScanStartTicksHigh) {
+		PERROR("Invalid scan start trigger argument specified.\n");
+		return ME_ERRNO_INVALID_SCAN_START_ARG;
+	}
+
+	switch (trigger->iScanStopTrigType) {
+	case ME_TRIG_TYPE_NONE:
+		if (trigger->iScanStopCount != 0) {
+			PERROR("Invalid scan stop count specified.\n");
+			return ME_ERRNO_INVALID_SCAN_STOP_ARG;
+		}
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iScanStopCount <= 0) {
+				PERROR("Invalid scan stop count specified.\n");
+				return ME_ERRNO_INVALID_SCAN_STOP_ARG;
+			}
+		} else {
+			PERROR("The continous mode has not 'scan' contects.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		}
+		break;
+
+	default:
+		PERROR("Invalid scan stop trigger type specified.\n");
+		return ME_ERRNO_INVALID_SCAN_STOP_TRIG_TYPE;
+	}
+
+	switch (trigger->iAcqStopTrigType) {
+	case ME_TRIG_TYPE_NONE:
+		if (trigger->iAcqStopCount != 0) {
+			PERROR("Invalid acq stop count specified.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_ARG;
+		}
+		break;
+
+	case ME_TRIG_TYPE_COUNT:
+		if (trigger->iScanStopTrigType != ME_TRIG_TYPE_NONE) {
+			PERROR("Invalid acq stop trigger type specified.\n");
+			return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+		}
+
+		if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {
+			if (trigger->iAcqStopCount <= 0) {
+				PERROR
+				    ("The continous mode has not 'scan' contects.\n");
+				return ME_ERRNO_INVALID_ACQ_STOP_ARG;
+			}
+		}
+//                      else
+//                      {
+//                              PERROR("Invalid acq stop trigger type specified.\n");
+//                              return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+//                      }
+
+		break;
+
+	default:
+		PERROR("Invalid acq stop trigger type specified.\n");
+		return ME_ERRNO_INVALID_ACQ_STOP_TRIG_TYPE;
+	}
+
+	switch (trigger->iAcqStartTrigChan) {
+	case ME_TRIG_CHAN_DEFAULT:
+	case ME_TRIG_CHAN_SYNCHRONOUS:
+		break;
+
+	default:
+		PERROR("Invalid acq start trigger channel specified.\n");
+		return ME_ERRNO_INVALID_ACQ_START_TRIG_CHAN;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Stop device
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	//Check if state machine is stopped.
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	//Reset control register. Block all actions. Disable IRQ. Disable FIFO.
+	ctrl = ME6000_AO_CTRL_BIT_IMMEDIATE_STOP | ME6000_AO_CTRL_BIT_STOP;
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+
+	//Reset interrupt latch
+	inl(instance->irq_reset_reg);
+
+	//This is paranoic, but to be sure.
+	instance->preloaded_count = 0;
+	instance->data_count = 0;
+	instance->circ_buf.head = 0;
+	instance->circ_buf.tail = 0;
+
+	/* Set mode. */
+	if (flags & ME_IO_STREAM_CONFIG_WRAPAROUND) {	//Wraparound
+		if (flags & ME_IO_STREAM_CONFIG_HARDWARE_ONLY) {	//Hardware wraparound
+			PINFO("Hardware wraparound.\n");
+			ctrl |= ME6000_AO_MODE_WRAPAROUND;
+			instance->mode = ME6000_AO_HW_WRAP_MODE;
+		} else {	//Software wraparound
+			PINFO("Software wraparound.\n");
+			ctrl |= ME6000_AO_MODE_CONTINUOUS;
+			instance->mode = ME6000_AO_SW_WRAP_MODE;
+		}
+	} else {		//Continous
+		PINFO("Continous.\n");
+		ctrl |= ME6000_AO_MODE_CONTINUOUS;
+		instance->mode = ME6000_AO_CONTINOUS;
+	}
+
+	//Set the trigger edge.
+	if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_EXT_DIGITAL) {	//Set the trigger type and edge for external trigger.
+		PINFO("External digital trigger.\n");
+		instance->start_mode = ME6000_AO_EXT_TRIG;
+
+		switch (trigger->iAcqStartTrigEdge) {
+		case ME_TRIG_EDGE_RISING:
+			PINFO("Set the trigger edge: rising.\n");
+			instance->ctrl_trg = 0x0;
+			break;
+
+		case ME_TRIG_EDGE_FALLING:
+			PINFO("Set the trigger edge: falling.\n");
+//                                      ctrl |= ME6000_AO_CTRL_BIT_EX_TRIG_EDGE;
+			instance->ctrl_trg = ME6000_AO_CTRL_BIT_EX_TRIG_EDGE;
+			break;
+
+		case ME_TRIG_EDGE_ANY:
+			PINFO("Set the trigger edge: both edges.\n");
+//                                      ctrl |= ME6000_AO_CTRL_BIT_EX_TRIG_EDGE | ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			instance->ctrl_trg =
+			    ME6000_AO_CTRL_BIT_EX_TRIG_EDGE |
+			    ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH;
+			break;
+		}
+	} else {
+		PINFO("Internal software trigger.\n");
+		instance->start_mode = 0;
+	}
+
+	//Set the stop mode and value.
+	if (trigger->iAcqStopTrigType == ME_TRIG_TYPE_COUNT) {	//Amount of data
+		instance->stop_mode = ME6000_AO_ACQ_STOP_MODE;
+		instance->stop_count = trigger->iAcqStopCount;
+	} else if (trigger->iScanStopTrigType == ME_TRIG_TYPE_COUNT) {	//Amount of 'scans'
+		instance->stop_mode = ME6000_AO_SCAN_STOP_MODE;
+		instance->stop_count = trigger->iScanStopCount;
+	} else {		//Infinite
+		instance->stop_mode = ME6000_AO_INF_STOP_MODE;
+		instance->stop_count = 0;
+	}
+
+	PINFO("Stop count: %d.\n", instance->stop_count);
+
+	if (trigger->iAcqStartTrigChan == ME_TRIG_CHAN_SYNCHRONOUS) {	//Synchronous start
+		instance->start_mode |= ME6000_AO_SYNC_HOLD;
+		if (trigger->iAcqStartTrigType == ME_TRIG_TYPE_EXT_DIGITAL) {	//Externaly triggered
+			PINFO("Synchronous start. Externaly trigger active.\n");
+			instance->start_mode |= ME6000_AO_SYNC_EXT_TRIG;
+		}
+#ifdef MEDEBUG_INFO
+		else {
+			PINFO
+			    ("Synchronous start. Externaly trigger dissabled.\n");
+		}
+#endif
+
+	}
+	//Set speed
+	outl(conv_ticks - 2, instance->timer_reg);
+	PDEBUG_REG("timer_reg outl(0x%lX+0x%lX)=0x%llx\n", instance->reg_base,
+		   instance->timer_reg - instance->reg_base, conv_ticks - 2);
+	instance->hardware_stop_delay = (int)(conv_ticks * HZ) / ME6000_AO_BASE_FREQUENCY;	//<== MUST be with cast!
+
+	// Write the control word
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+
+	//Set status.
+	instance->status = ao_status_stream_configured;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_stream_new_values(me_subdevice_t * subdevice,
+					  struct file *filep,
+					  int time_out, int *count, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	long j;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!instance->circ_buf.buf) {
+		PERROR("Circular buffer not exists.\n");
+		return ME_ERRNO_INTERNAL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (me_circ_buf_space(&instance->circ_buf)) {	//The buffer is NOT full.
+		*count = me_circ_buf_space(&instance->circ_buf);
+	} else {		//The buffer is full.
+		if (time_out) {
+			t = (time_out * HZ) / 1000;
+
+			if (t == 0)
+				t = 1;
+		} else {	//Max time.
+			t = LONG_MAX;
+		}
+
+		*count = 0;
+
+		j = jiffies;
+
+		//Only runing process will interrupt this call. Interrupts are when FIFO HF is signaled.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((me_circ_buf_space
+						   (&instance->circ_buf))
+						  || !(inl(instance->status_reg)
+						       &
+						       ME6000_AO_STATUS_BIT_FSM)),
+						 t);
+
+		if (!(inl(instance->status_reg) & ME6000_AO_STATUS_BIT_FSM)) {
+			PERROR("AO subdevice is not running.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+		} else if (signal_pending(current)) {
+			PERROR("Wait on values interrupted from signal.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		} else if ((jiffies - j) >= t) {
+			PERROR("Wait on values timed out.\n");
+			err = ME_ERRNO_TIMEOUT;
+		} else {	//Uff... all is good. Inform user about empty space.
+			*count = me_circ_buf_space(&instance->circ_buf);
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_stream_start(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int start_mode, int time_out, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long cpu_flags = 0;
+	uint32_t status;
+	uint32_t ctrl;
+	uint32_t synch;
+	int count = 0;
+	int circ_buffer_count;
+
+	unsigned long ref;
+	unsigned long delay = 0;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags & ~ME_IO_STREAM_START_TYPE_TRIG_SYNCHRONOUS) {
+		PERROR("Invalid flags.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid timeout specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if ((start_mode != ME_START_MODE_BLOCKING)
+	    && (start_mode != ME_START_MODE_NONBLOCKING)) {
+		PERROR("Invalid start mode specified.\n");
+		return ME_ERRNO_INVALID_START_MODE;
+	}
+
+	if (time_out) {
+		delay = (time_out * HZ) / 1000;
+		if (delay == 0)
+			delay = 1;
+	}
+
+	switch (instance->status) {	//Checking actual mode.
+	case ao_status_stream_configured:
+	case ao_status_stream_end:
+		//Correct modes!
+		break;
+
+		//The device is in wrong mode.
+	case ao_status_none:
+	case ao_status_single_configured:
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+		PERROR
+		    ("Subdevice must be preinitialize correctly for streaming.\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		PDEBUG("Before restart broke stream 'STOP' must be caled.\n");
+		return ME_STATUS_ERROR;
+
+	case ao_status_stream_run_wait:
+	case ao_status_stream_run:
+	case ao_status_stream_end_wait:
+		PDEBUG("Stream is already working.\n");
+		return ME_ERRNO_SUBDEVICE_BUSY;
+
+	default:
+		instance->status = ao_status_stream_error;
+		PERROR_CRITICAL("Status is in wrong state!\n");
+		return ME_ERRNO_INTERNAL;
+
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->mode == ME6000_AO_CONTINOUS) {	//Continous
+		instance->circ_buf.tail += instance->preloaded_count;
+		instance->circ_buf.tail &= instance->circ_buf.mask;
+	}
+	circ_buffer_count = me_circ_buf_values(&instance->circ_buf);
+
+	if (!circ_buffer_count && !instance->preloaded_count) {	//No values in buffer
+		ME_SUBDEVICE_EXIT;
+		PERROR("No values in buffer!\n");
+		return ME_ERRNO_LACK_OF_RESOURCES;
+	}
+
+	//Cancel control task
+	PDEBUG("Cancel control task. idx=%d\n", instance->ao_idx);
+	instance->ao_control_task_flag = 0;
+	cancel_delayed_work(&instance->ao_control_task);
+
+	//Stop device
+	err = ao_stop_immediately(instance);
+	if (err) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		ME_SUBDEVICE_EXIT;
+
+		return ME_ERRNO_SUBDEVICE_BUSY;
+	}
+	//Set values for single_read()
+	instance->single_value = ME6000_AO_MAX_DATA + 1;
+	instance->single_value_in_fifo = ME6000_AO_MAX_DATA + 1;
+
+	//Setting stop points
+	if (instance->stop_mode == ME6000_AO_SCAN_STOP_MODE) {
+		instance->stop_data_count =
+		    instance->stop_count * circ_buffer_count;
+	} else {
+		instance->stop_data_count = instance->stop_count;
+	}
+
+	if ((instance->stop_data_count != 0)
+	    && (instance->stop_data_count < circ_buffer_count)) {
+		PERROR("More data in buffer than previously set limit!\n");
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	ctrl = inl(instance->ctrl_reg);
+	//Check FIFO
+	if (!(ctrl & ME6000_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO wasn't enabeled. Do it. <= This should be done by user call with ME_WRITE_MODE_PRELOAD
+		PINFO("Enableing FIFO.\n");
+		ctrl |= ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+
+		instance->preloaded_count = 0;
+		instance->data_count = 0;
+	} else {		//Block IRQ
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+
+	//Reset interrupt latch
+	inl(instance->irq_reset_reg);
+
+	//Fill FIFO <= Generaly this should be done by user pre-load call but this is second place to do it.
+	status = inl(instance->status_reg);
+	if (!(status & ME6000_AO_STATUS_BIT_EF)) {	//FIFO empty
+		if (instance->stop_data_count != 0) {
+			count = ME6000_AO_FIFO_COUNT;
+		} else {
+			count =
+			    (ME6000_AO_FIFO_COUNT <
+			     instance->
+			     stop_data_count) ? ME6000_AO_FIFO_COUNT :
+			    instance->stop_data_count;
+		}
+
+		//Copy data
+		count =
+		    ao_write_data(instance, count, instance->preloaded_count);
+
+		if (count < 0) {	//This should never happend!
+			PERROR_CRITICAL("COPY FINISH WITH ERROR!\n");
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+	}
+	//Set pre-load features.
+	spin_lock(instance->preload_reg_lock);
+	synch = inl(instance->preload_reg);
+	synch &=
+	    ~((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) << instance->
+	      ao_idx);
+	synch |=
+	    (instance->start_mode & ~ME6000_AO_EXT_TRIG) << instance->ao_idx;
+	outl(synch, instance->preload_reg);
+	PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->preload_reg - instance->reg_base, synch);
+	spin_unlock(instance->preload_reg_lock);
+
+	//Default count is '0'
+	if (instance->mode == ME6000_AO_CONTINOUS) {	//Continous
+		instance->preloaded_count = 0;
+		instance->circ_buf.tail += count;
+		instance->circ_buf.tail &= instance->circ_buf.mask;
+	} else {		//Wraparound
+		instance->preloaded_count += count;
+		instance->data_count += count;
+
+		//Special case: Infinite wraparound with less than FIFO datas always should runs in hardware mode.
+		if ((instance->stop_mode == ME6000_AO_INF_STOP_MODE)
+		    && (circ_buffer_count <= ME6000_AO_FIFO_COUNT)) {	//Change to hardware wraparound
+			PDEBUG
+			    ("Changeing mode from software wraparound to hardware wraparound.\n");
+			//Copy all data
+			count =
+			    ao_write_data(instance, circ_buffer_count,
+					  instance->preloaded_count);
+			ctrl &= ~ME6000_AO_CTRL_MODE_MASK;
+			ctrl |= ME6000_AO_MODE_WRAPAROUND;
+		}
+
+		if (instance->preloaded_count == me_circ_buf_values(&instance->circ_buf)) {	//Reset position indicator.
+			instance->preloaded_count = 0;
+		} else if (instance->preloaded_count > me_circ_buf_values(&instance->circ_buf)) {	//This should never happend!
+			PERROR_CRITICAL
+			    ("PRELOADED MORE VALUES THAN ARE IN BUFFER!\n");
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+	}
+
+	//Set status to 'wait for start'
+	instance->status = ao_status_stream_run_wait;
+
+	status = inl(instance->status_reg);
+	//Start state machine and interrupts
+	PINFO("<%s:%d> Start state machine.\n", __FUNCTION__, __LINE__);
+	ctrl &= ~(ME6000_AO_CTRL_BIT_STOP | ME6000_AO_CTRL_BIT_IMMEDIATE_STOP);
+	if (instance->start_mode == ME6000_AO_EXT_TRIG) {
+		PDEBUG("DIGITAL TRIGGER\n");
+		ctrl |= ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG;
+	}
+	if (!(status & ME6000_AO_STATUS_BIT_HF)) {	//More than half!
+		if ((ctrl & ME6000_AO_CTRL_MODE_MASK) == ME6000_AO_MODE_CONTINUOUS) {	//Enable IRQ only when hardware_continous is set and FIFO is more than half
+			PINFO("<%s:%d> Start interrupts.\n", __FUNCTION__,
+			      __LINE__);
+			ctrl |= ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+		}
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	//Trigger output
+	PINFO("<%s> start mode= 0x%x %s\n", __FUNCTION__, instance->start_mode,
+	      (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) ? "SYNCHRONOUS" :
+	      "");
+	if (flags & ME_IO_SINGLE_TYPE_TRIG_SYNCHRONOUS) {	//Trigger outputs
+		spin_lock(instance->preload_reg_lock);
+		synch = inl(instance->preload_reg);
+		//Add channel to start list
+		outl(synch | (ME6000_AO_SYNC_HOLD << instance->ao_idx),
+		     instance->preload_reg);
+		PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->preload_reg - instance->reg_base,
+			   synch | (ME6000_AO_SYNC_HOLD << instance->ao_idx));
+
+		//Fire
+		PINFO
+		    ("Fired all software synchronous outputs by software trigger.\n");
+		outl(0x8000, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, 0x8000);
+
+		//Restore save settings
+		outl(synch, instance->preload_reg);
+		PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->preload_reg - instance->reg_base, synch);
+		spin_unlock(instance->preload_reg_lock);
+	} else if (!instance->start_mode) {	//Trigger outputs
+/*
+		spin_lock(instance->preload_reg_lock);
+			synch = inl(instance->preload_reg);
+			//Remove channel from start list
+			outl(synch & ~(ME6000_AO_SYNC_HOLD << instance->ao_idx), instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, synch & ~(ME6000_AO_SYNC_HOLD << instance->ao_idx));
+*/
+		//Fire
+		PINFO("Software trigger.\n");
+		outl(0x8000, instance->single_reg);
+		PDEBUG_REG("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->single_reg - instance->reg_base, 0x8000);
+
+/*
+			//Restore save settings
+			outl(synch, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base, instance->preload_reg - instance->reg_base, synch);
+		spin_unlock(instance->preload_reg_lock);
+*/
+	}
+	// Set control task's timeout
+	instance->timeout.delay = delay;
+	instance->timeout.start_time = jiffies;
+
+	if (status & ME6000_AO_STATUS_BIT_HF) {	//Less than half but not empty!
+		PINFO("Less than half.\n");
+		if (instance->stop_data_count == 0) {
+			count = ME6000_AO_FIFO_COUNT / 2;
+		} else {
+			count =
+			    ((ME6000_AO_FIFO_COUNT / 2) <
+			     instance->stop_data_count) ? ME6000_AO_FIFO_COUNT /
+			    2 : instance->stop_data_count;
+		}
+
+		//Copy data
+		count =
+		    ao_write_data(instance, count, instance->preloaded_count);
+
+		if (count < 0) {	//This should never happend!
+			PERROR_CRITICAL("COPY FINISH WITH ERROR!\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+
+		if (instance->mode == ME6000_AO_CONTINOUS) {	//Continous
+			instance->circ_buf.tail += count;
+			instance->circ_buf.tail &= instance->circ_buf.mask;
+		} else {	//Wraparound
+			instance->data_count += count;
+			instance->preloaded_count += count;
+
+			if (instance->preloaded_count == me_circ_buf_values(&instance->circ_buf)) {	//Reset position indicator.
+				instance->preloaded_count = 0;
+			} else if (instance->preloaded_count > me_circ_buf_values(&instance->circ_buf)) {	//This should never happend!
+				PERROR_CRITICAL
+				    ("PRELOADED MORE VALUES THAN ARE IN BUFFER!\n");
+				ME_SUBDEVICE_EXIT;
+				return ME_ERRNO_INTERNAL;
+			}
+		}
+
+		status = inl(instance->status_reg);
+		if (!(status & ME6000_AO_STATUS_BIT_HF)) {	//More than half!
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			PINFO("<%s:%d> Start interrupts.\n", __FUNCTION__,
+			      __LINE__);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |= ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+		}
+	}
+	//Special case: Limited wraparound with less than HALF FIFO datas need work around to generate first interrupt.
+	if ((instance->stop_mode != ME6000_AO_INF_STOP_MODE)
+	    && (instance->mode == ME6000_AO_SW_WRAP_MODE)
+	    && (circ_buffer_count <= (ME6000_AO_FIFO_COUNT / 2))) {	//Put more data to FIFO
+		PINFO("Limited wraparound with less than HALF FIFO datas.\n");
+		if (instance->preloaded_count) {	//This should never happend!
+			PERROR_CRITICAL
+			    ("ERROR WHEN LOADING VALUES FOR WRAPAROUND!\n");
+			ME_SUBDEVICE_EXIT;
+			return ME_ERRNO_INTERNAL;
+		}
+
+		while (instance->stop_data_count > instance->data_count) {	//Maximum data not set jet.
+			//Copy to buffer
+			if (circ_buffer_count != ao_write_data(instance, circ_buffer_count, 0)) {	//This should never happend!
+				PERROR_CRITICAL
+				    ("ERROR WHEN LOADING VALUES FOR WRAPAROUND!\n");
+				ME_SUBDEVICE_EXIT;
+				return ME_ERRNO_INTERNAL;
+			}
+			instance->data_count += circ_buffer_count;
+
+			if (!((status = inl(instance->status_reg)) & ME6000_AO_STATUS_BIT_HF)) {	//FIFO is more than half. Enable IRQ and end copy.
+				//Reset interrupt latch
+				inl(instance->irq_reset_reg);
+
+				spin_lock_irqsave(&instance->subdevice_lock,
+						  cpu_flags);
+				PINFO("<%s:%d> Start interrupts.\n",
+				      __FUNCTION__, __LINE__);
+				ctrl = inl(instance->ctrl_reg);
+				ctrl |= ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+				outl(ctrl, instance->ctrl_reg);
+				PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->ctrl_reg -
+					   instance->reg_base, ctrl);
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+				break;
+			}
+		}
+	}
+	// Schedule control task
+	instance->ao_control_task_flag = 1;
+	queue_delayed_work(instance->me6000_workqueue,
+			   &instance->ao_control_task, 1);
+
+	if (start_mode == ME_START_MODE_BLOCKING) {	//Wait for start.
+		ref = jiffies;
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_stream_run_wait),
+						 (delay) ? delay +
+						 1 : LONG_MAX);
+
+		if ((instance->status != ao_status_stream_run)
+		    && (instance->status != ao_status_stream_end)) {
+			PDEBUG("Starting stream canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait on start of state machine interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		if ((delay) && ((jiffies - ref) >= delay)) {
+			if (instance->status != ao_status_stream_run) {
+				if (instance->status == ao_status_stream_end) {
+					PDEBUG("Timeout reached.\n");
+				} else if ((jiffies - ref) > delay) {
+					PERROR
+					    ("Timeout reached. Not handled by control task!\n");
+					ao_stop_immediately(instance);
+				} else {
+					PERROR
+					    ("Timeout reached. Signal come but status is strange: %d\n",
+					     instance->status);
+					ao_stop_immediately(instance);
+				}
+
+				instance->ao_control_task_flag = 0;
+				cancel_delayed_work(&instance->ao_control_task);
+				instance->status = ao_status_stream_end;
+				err = ME_ERRNO_TIMEOUT;
+			}
+		}
+	}
+
+	ME_SUBDEVICE_EXIT;
+	return err;
+}
+
+static int me6000_ao_io_stream_status(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int wait,
+				      int *status, int *values, int flags)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((wait != ME_WAIT_NONE) && (wait != ME_WAIT_IDLE)) {
+		PERROR("Invalid wait argument specified.\n");
+		*status = ME_STATUS_INVALID;
+		return ME_ERRNO_INVALID_WAIT;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	switch (instance->status) {
+	case ao_status_single_configured:
+	case ao_status_single_end:
+	case ao_status_stream_configured:
+	case ao_status_stream_end:
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		*status = ME_STATUS_IDLE;
+		break;
+
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+	case ao_status_stream_run_wait:
+	case ao_status_stream_run:
+	case ao_status_stream_end_wait:
+		*status = ME_STATUS_BUSY;
+		break;
+
+	case ao_status_none:
+	default:
+		*status =
+		    (inl(instance->status_reg) & ME6000_AO_STATUS_BIT_FSM) ?
+		    ME_STATUS_BUSY : ME_STATUS_IDLE;
+		break;
+	}
+
+	if ((wait == ME_WAIT_IDLE) && (*status == ME_STATUS_BUSY)) {
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 ((instance->status !=
+						   ao_status_single_run_wait)
+						  && (instance->status !=
+						      ao_status_single_run)
+						  && (instance->status !=
+						      ao_status_single_end_wait)
+						  && (instance->status !=
+						      ao_status_stream_run_wait)
+						  && (instance->status !=
+						      ao_status_stream_run)
+						  && (instance->status !=
+						      ao_status_stream_end_wait)),
+						 LONG_MAX);
+
+		if (instance->status != ao_status_stream_end) {
+			PDEBUG("Wait for IDLE canceled. %d\n",
+			       instance->status);
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Wait for IDLE interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+
+		*status = ME_STATUS_IDLE;
+	}
+
+	*values = me_circ_buf_space(&instance->circ_buf);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_stream_stop(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int stop_mode, int flags)
+{				/// @note Stop work and empty buffer and FIFO
+	int err = ME_ERRNO_SUCCESS;
+	me6000_ao_subdevice_t *instance;
+	unsigned long cpu_flags;
+	volatile uint32_t ctrl;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (flags & ~ME_IO_STREAM_STOP_PRESERVE_BUFFERS) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if ((stop_mode != ME_STOP_MODE_IMMEDIATE)
+	    && (stop_mode != ME_STOP_MODE_LAST_VALUE)) {
+		PERROR("Invalid stop mode specified.\n");
+		return ME_ERRNO_INVALID_STOP_MODE;
+	}
+
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (instance->status < ao_status_stream_configured) {
+		//There is nothing to stop!
+		PERROR("Subdevice not in streaming mode. %d\n",
+		       instance->status);
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	//Mark as stopping. => Software stop.
+	instance->status = ao_status_stream_end_wait;
+
+	if (stop_mode == ME_STOP_MODE_IMMEDIATE) {	//Stopped now!
+		err = ao_stop_immediately(instance);
+	} else if (stop_mode == ME_STOP_MODE_LAST_VALUE) {
+		ctrl = inl(instance->ctrl_reg) & ME6000_AO_CTRL_MODE_MASK;
+		if (ctrl == ME6000_AO_MODE_WRAPAROUND) {	//Hardware wraparound => Hardware stop.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |= ME6000_AO_CTRL_BIT_STOP;
+			ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			//Reset interrupt latch
+			inl(instance->irq_reset_reg);
+		}
+		//Only runing process will interrupt this call. Events are signaled when status change. Extra timeout add for safe reason.
+		wait_event_interruptible_timeout(instance->wait_queue,
+						 (instance->status !=
+						  ao_status_stream_end_wait),
+						 LONG_MAX);
+
+		if (instance->status != ao_status_stream_end) {
+			PDEBUG("Stopping stream canceled.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+
+		if (signal_pending(current)) {
+			PERROR("Stopping stream interrupted.\n");
+			instance->status = ao_status_none;
+			ao_stop_immediately(instance);
+			err = ME_ERRNO_SIGNAL;
+		}
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	ctrl = inl(instance->ctrl_reg);
+	ctrl |= ME6000_AO_CTRL_BIT_STOP | ME6000_AO_CTRL_BIT_IMMEDIATE_STOP;
+	ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+	if (!flags) {		//Reset FIFO
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+	}
+	outl(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	//Reset interrupt latch
+	inl(instance->irq_reset_reg);
+
+	if (!flags) {		//Reset software buffer
+		instance->circ_buf.head = 0;
+		instance->circ_buf.tail = 0;
+		instance->preloaded_count = 0;
+		instance->data_count = 0;
+	}
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_ao_io_stream_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int write_mode,
+				     int *values, int *count, int flags)
+{
+	int err = ME_ERRNO_SUCCESS;
+	me6000_ao_subdevice_t *instance;
+	unsigned long cpu_flags = 0;
+	uint32_t reg_copy;
+
+	int copied_from_user = 0;
+	int left_to_copy_from_user = *count;
+
+	int copied_values;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	//Checking arguments
+	if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {
+		PERROR("Not a streaming ao.\n");
+		return ME_ERRNO_NOT_SUPPORTED;
+	}
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (*count <= 0) {
+		PERROR("Invalid count of values specified.\n");
+		return ME_ERRNO_INVALID_VALUE_COUNT;
+	}
+
+	if (values == NULL) {
+		PERROR("Invalid address of values specified.\n");
+		return ME_ERRNO_INVALID_POINTER;
+	}
+
+	if ((instance->status == ao_status_none) || (instance->status == ao_status_single_configured)) {	//The device is in single mode.
+		PERROR
+		    ("Subdevice must be preinitialize correctly for streaming.\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+	}
+
+	switch (write_mode) {
+	case ME_WRITE_MODE_PRELOAD:
+
+		//Device must be stopped.
+		if ((instance->status != ao_status_stream_configured)
+		    && (instance->status != ao_status_stream_end)) {
+			PERROR
+			    ("Subdevice mustn't be runing when 'pre-load' mode is used.\n");
+			return ME_ERRNO_PREVIOUS_CONFIG;
+		}
+		break;
+	case ME_WRITE_MODE_NONBLOCKING:
+	case ME_WRITE_MODE_BLOCKING:
+		/// @note In blocking mode: When device is not runing and there is not enought space call will blocked up!
+		/// @note Some other thread must empty buffer by strating engine.
+		break;
+
+	default:
+		PERROR("Invalid write mode specified.\n");
+		return ME_ERRNO_INVALID_WRITE_MODE;
+	}
+
+	if (instance->mode & ME6000_AO_WRAP_MODE) {	//Wraparound mode. Device must be stopped.
+		if ((instance->status != ao_status_stream_configured)
+		    && (instance->status != ao_status_stream_end)) {
+			PERROR
+			    ("Subdevice mustn't be runing when 'pre-load' mode is used.\n");
+			return ME_ERRNO_INVALID_WRITE_MODE;
+		}
+	}
+
+	if ((instance->mode == ME6000_AO_HW_WRAP_MODE)
+	    && (write_mode != ME_WRITE_MODE_PRELOAD)) {
+/*
+		PERROR("Only 'pre-load' write is acceptable in hardware wraparound mode.\n");
+		return ME_ERRNO_PREVIOUS_CONFIG;
+*/
+		//This is transparent for user.
+		PDEBUG("Changing write_mode to ME_WRITE_MODE_PRELOAD.\n");
+		write_mode = ME_WRITE_MODE_PRELOAD;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (write_mode == ME_WRITE_MODE_PRELOAD) {	//Init enviroment - preload
+		spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+		reg_copy = inl(instance->ctrl_reg);
+		//Check FIFO
+		if (!(reg_copy & ME6000_AO_CTRL_BIT_ENABLE_FIFO)) {	//FIFO not active. Enable it.
+			reg_copy |= ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			instance->preloaded_count = 0;
+		}
+		spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	}
+
+	while (1) {
+		//Copy to buffer. This step is common for all modes.
+		copied_from_user =
+		    ao_get_data_from_user(instance, left_to_copy_from_user,
+					  values + (*count -
+						    left_to_copy_from_user));
+		left_to_copy_from_user -= copied_from_user;
+
+		reg_copy = inl(instance->status_reg);
+		if ((instance->status == ao_status_stream_run) && !(reg_copy & ME6000_AO_STATUS_BIT_FSM)) {	//BROKEN PIPE! The state machine is stoped but logical status show that should be working.
+			PERROR("Broken pipe in write.\n");
+			err = ME_ERRNO_SUBDEVICE_NOT_RUNNING;
+			break;
+		}
+
+		if ((instance->status == ao_status_stream_run) && (instance->mode == ME6000_AO_CONTINOUS) && (reg_copy & ME6000_AO_STATUS_BIT_HF)) {	//Continous mode runing and data are below half!
+
+			// Block interrupts.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			reg_copy = inl(instance->ctrl_reg);
+			reg_copy &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			//Fast copy
+			copied_values =
+			    ao_write_data(instance, ME6000_AO_FIFO_COUNT / 2,
+					  0);
+			if (copied_values > 0) {
+				instance->circ_buf.tail += copied_values;
+				instance->circ_buf.tail &=
+				    instance->circ_buf.mask;
+				continue;
+			}
+			//Reset interrupt latch
+			inl(instance->irq_reset_reg);
+
+			// Activate interrupts.
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			reg_copy = inl(instance->ctrl_reg);
+			reg_copy |= ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			outl(reg_copy, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   reg_copy);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (copied_values == 0) {	//This was checked and never should happend!
+				PERROR_CRITICAL("COPY FINISH WITH 0!\n");
+			}
+
+			if (copied_values < 0) {	//This was checked and never should happend!
+				PERROR_CRITICAL("COPY FINISH WITH ERROR!\n");
+				instance->status = ao_status_stream_fifo_error;
+				err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+				break;
+			}
+		}
+
+		if (!left_to_copy_from_user) {	//All datas were copied.
+			break;
+		} else {	//Not all datas were copied.
+			if (instance->mode & ME6000_AO_WRAP_MODE) {	//Error too much datas! Wraparound is limited in size!
+				PERROR
+				    ("Too much data for wraparound mode!  Exceeded size of %ld.\n",
+				     ME6000_AO_CIRC_BUF_COUNT - 1);
+				err = ME_ERRNO_RING_BUFFER_OVERFLOW;
+				break;
+			}
+
+			if (write_mode != ME_WRITE_MODE_BLOCKING) {	//Non blocking calls
+				break;
+			}
+
+			wait_event_interruptible(instance->wait_queue,
+						 me_circ_buf_space(&instance->
+								   circ_buf));
+
+			if (signal_pending(current)) {
+				PERROR("Writing interrupted by signal.\n");
+				instance->status = ao_status_none;
+				ao_stop_immediately(instance);
+				err = ME_ERRNO_SIGNAL;
+				break;
+			}
+
+			if (instance->status == ao_status_none) {	//Reset
+				PERROR("Writing interrupted by reset.\n");
+				err = ME_ERRNO_CANCELLED;
+				break;
+			}
+		}
+	}
+
+	if (write_mode == ME_WRITE_MODE_PRELOAD) {	//Copy data to FIFO - preload
+		copied_values =
+		    ao_write_data_pooling(instance, ME6000_AO_FIFO_COUNT,
+					  instance->preloaded_count);
+		instance->preloaded_count += copied_values;
+		instance->data_count += copied_values;
+
+		if ((instance->mode == ME6000_AO_HW_WRAP_MODE)
+		    && (me_circ_buf_values(&instance->circ_buf) >
+			ME6000_AO_FIFO_COUNT)) {
+			PERROR
+			    ("Too much data for hardware wraparound mode! Exceeded size of %d.\n",
+			     ME6000_AO_FIFO_COUNT);
+			err = ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+		}
+	}
+
+	*count = *count - left_to_copy_from_user;
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me6000_ao_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me6000_ao_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me6000_ao_subdevice_t *instance = dev_id;
+	uint32_t irq_status;
+	uint32_t ctrl;
+	uint32_t status;
+	int count = 0;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inl(instance->irq_status_reg);
+	if (!(irq_status & (ME6000_IRQ_STATUS_BIT_AO_HF << instance->ao_idx))) {
+		PINFO("%ld Shared interrupt. %s(): ID=%d: status_reg=0x%04X\n",
+		      jiffies, __FUNCTION__, instance->ao_idx, irq_status);
+		return IRQ_NONE;
+	}
+
+	if (!instance->circ_buf.buf) {
+		instance->status = ao_status_stream_error;
+		PERROR_CRITICAL("CIRCULAR BUFFER NOT EXISTS!\n");
+		//Block interrupts. Stop machine.
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+		ctrl |=
+		    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP | ME6000_AO_CTRL_BIT_STOP;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		//Inform user
+		wake_up_interruptible_all(&instance->wait_queue);
+		return IRQ_HANDLED;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME6000_AO_STATUS_BIT_FSM)) {	//Too late. Not working! END? BROKEN PIPE?
+		/// @note Error checking was moved to separate task.
+		PDEBUG("Interrupt come but ISM is not working!\n");
+		//Block interrupts. Stop machine.
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+		ctrl |=
+		    ME6000_AO_CTRL_BIT_STOP | ME6000_AO_CTRL_BIT_IMMEDIATE_STOP;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		//Reset interrupt latch
+		inl(instance->irq_reset_reg);
+
+		/// @note User notification was also moved to separate task.
+		return IRQ_HANDLED;
+	}
+	//General procedure. Process more datas.
+
+#ifdef MEDEBUG_DEBUG
+	if (!me_circ_buf_values(&instance->circ_buf)) {	//Buffer is empty!
+		PDEBUG("Circular buffer empty!\n");
+	}
+#endif
+
+	//Check FIFO
+	if (status & ME6000_AO_STATUS_BIT_HF) {	//OK less than half
+
+		//Block interrupts
+		ctrl = inl(instance->ctrl_reg);
+		ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+
+		do {
+			//Calculate how many should be copied.
+			count =
+			    (instance->stop_data_count) ? instance->
+			    stop_data_count -
+			    instance->data_count : ME6000_AO_FIFO_COUNT / 2;
+			if (ME6000_AO_FIFO_COUNT / 2 < count) {
+				count = ME6000_AO_FIFO_COUNT / 2;
+			}
+			//Copy data
+			if (instance->mode == ME6000_AO_CONTINOUS) {	//Continous
+				count = ao_write_data(instance, count, 0);
+				if (count > 0) {
+					instance->circ_buf.tail += count;
+					instance->circ_buf.tail &=
+					    instance->circ_buf.mask;
+					instance->data_count += count;
+
+					if ((instance->status == ao_status_stream_end_wait) && !me_circ_buf_values(&instance->circ_buf)) {	//Stoping. Whole buffer was copied.
+						break;
+					}
+				}
+			} else if ((instance->mode == ME6000_AO_SW_WRAP_MODE) && ((ctrl & ME6000_AO_CTRL_MODE_MASK) == ME6000_AO_MODE_CONTINUOUS)) {	//Wraparound (software)
+				if (instance->status == ao_status_stream_end_wait) {	//We stoping => Copy to the end of the buffer.
+					count =
+					    ao_write_data(instance, count, 0);
+				} else {	//Copy in wraparound mode.
+					count =
+					    ao_write_data_wraparound(instance,
+								     count,
+								     instance->
+								     preloaded_count);
+				}
+
+				if (count > 0) {
+					instance->data_count += count;
+					instance->preloaded_count += count;
+					instance->preloaded_count %=
+					    me_circ_buf_values(&instance->
+							       circ_buf);
+
+					if ((instance->status == ao_status_stream_end_wait) && !instance->preloaded_count) {	//Stoping. Whole buffer was copied.
+						break;
+					}
+				}
+			}
+
+			if ((count <= 0) || (instance->stop_data_count && (instance->stop_data_count <= instance->data_count))) {	//End of work.
+				break;
+			}
+		}		//Repeat if still is under half fifo
+		while ((status =
+			inl(instance->status_reg)) & ME6000_AO_STATUS_BIT_HF);
+
+		//Unblock interrupts
+		ctrl = inl(instance->ctrl_reg);
+		if (count >= 0) {	//Copy was successful.
+			if (instance->stop_data_count && (instance->stop_data_count <= instance->data_count)) {	//Finishing work. No more interrupts.
+				PDEBUG("Finishing work. Interrupt disabled.\n");
+				instance->status = ao_status_stream_end_wait;
+			} else if (count > 0) {	//Normal work. Enable interrupt.
+				PDEBUG("Normal work. Enable interrupt.\n");
+				ctrl |= ME6000_AO_CTRL_BIT_ENABLE_IRQ;
+			} else {	//Normal work but there are no more data in buffer. Interrupt blocked. stream_write() will unblock it.
+				PDEBUG
+				    ("No data in software buffer. Interrupt blocked.\n");
+			}
+		} else {	//Error during copy.
+			instance->status = ao_status_stream_fifo_error;
+		}
+
+		outl(ctrl, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, ctrl);
+	} else {		//?? more than half
+		PDEBUG
+		    ("Interrupt come but FIFO more than half full! Reset interrupt.\n");
+	}
+
+	PINFO("ISR: Buffer count: %d.(T:%d H:%d)\n",
+	      me_circ_buf_values(&instance->circ_buf), instance->circ_buf.tail,
+	      instance->circ_buf.head);
+	PINFO("ISR: Stop count: %d.\n", instance->stop_count);
+	PINFO("ISR: Stop data count: %d.\n", instance->stop_data_count);
+	PINFO("ISR: Data count: %d.\n", instance->data_count);
+
+	//Reset interrupt latch
+	inl(instance->irq_reset_reg);
+
+	//Inform user
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+static void me6000_ao_destructor(struct me_subdevice *subdevice)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	instance->ao_control_task_flag = 0;
+
+	// Reset subdevice to asure clean exit.
+	me6000_ao_io_reset_subdevice(subdevice, NULL,
+				     ME_IO_RESET_SUBDEVICE_NO_FLAGS);
+
+	// Remove any tasks from work queue. This is paranoic because it was done allready in reset().
+	if (!cancel_delayed_work(&instance->ao_control_task)) {	//Wait 2 ticks to be sure that control task is removed from queue.
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(2);
+	}
+
+	if (instance->fifo & ME6000_AO_HAS_FIFO) {
+		if (instance->irq) {
+			free_irq(instance->irq, instance);
+			instance->irq = 0;
+		}
+
+		if (instance->circ_buf.buf) {
+			PDEBUG("free circ_buf = %p size=%d",
+			       instance->circ_buf.buf,
+			       PAGE_SHIFT << ME6000_AO_CIRC_BUF_SIZE_ORDER);
+			free_pages((unsigned long)instance->circ_buf.buf,
+				   ME6000_AO_CIRC_BUF_SIZE_ORDER);
+		}
+		instance->circ_buf.buf = NULL;
+	}
+
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+me6000_ao_subdevice_t *me6000_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     uint32_t * triggering_flags,
+					     int ao_idx,
+					     int fifo,
+					     int irq,
+					     int high_range,
+					     struct workqueue_struct *me6000_wq)
+{
+	me6000_ao_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed ID=%d.\n", ao_idx);
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me6000_ao_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me6000_ao_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->preload_reg_lock = preload_reg_lock;
+	subdevice->preload_flags = preload_flags;
+	subdevice->triggering_flags = triggering_flags;
+
+	/* Store analog output index */
+	subdevice->ao_idx = ao_idx;
+
+	/* Store if analog output has fifo */
+	subdevice->fifo = fifo;
+
+	if (subdevice->fifo & ME6000_AO_HAS_FIFO) {
+		/* Allocate and initialize circular buffer */
+		subdevice->circ_buf.mask = ME6000_AO_CIRC_BUF_COUNT - 1;
+		subdevice->circ_buf.buf =
+		    (void *)__get_free_pages(GFP_KERNEL,
+					     ME6000_AO_CIRC_BUF_SIZE_ORDER);
+		PDEBUG("circ_buf = %p size=%ld\n", subdevice->circ_buf.buf,
+		       ME6000_AO_CIRC_BUF_SIZE);
+
+		if (!subdevice->circ_buf.buf) {
+			PERROR
+			    ("Cannot initialize subdevice base class instance.\n");
+			kfree(subdevice);
+			return NULL;
+		}
+
+		memset(subdevice->circ_buf.buf, 0, ME6000_AO_CIRC_BUF_SIZE);
+	} else {
+		subdevice->circ_buf.mask = 0;
+		subdevice->circ_buf.buf = NULL;
+	}
+	subdevice->circ_buf.head = 0;
+	subdevice->circ_buf.tail = 0;
+
+	subdevice->status = ao_status_none;
+	subdevice->ao_control_task_flag = 0;
+	subdevice->timeout.delay = 0;
+	subdevice->timeout.start_time = jiffies;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Initialize single value to 0V */
+	subdevice->single_value = 0x8000;
+	subdevice->single_value_in_fifo = 0x8000;
+
+	/* Initialize range boarders */
+	if (high_range) {
+		subdevice->min = ME6000_AO_MIN_RANGE_HIGH;
+		subdevice->max = ME6000_AO_MAX_RANGE_HIGH;
+	} else {
+		subdevice->min = ME6000_AO_MIN_RANGE;
+		subdevice->max = ME6000_AO_MAX_RANGE;
+	}
+
+	/* Register interrupt service routine */
+
+	if (subdevice->fifo & ME6000_AO_HAS_FIFO) {
+		subdevice->irq = irq;
+		if (request_irq(subdevice->irq, me6000_ao_isr,
+#ifdef IRQF_DISABLED
+				IRQF_DISABLED | IRQF_SHARED,
+#else
+				SA_INTERRUPT | SA_SHIRQ,
+#endif
+				ME6000_NAME, subdevice)) {
+			PERROR("Cannot get interrupt line.\n");
+			PDEBUG("free circ_buf = %p size=%d",
+			       subdevice->circ_buf.buf,
+			       PAGE_SHIFT << ME6000_AO_CIRC_BUF_SIZE_ORDER);
+			free_pages((unsigned long)subdevice->circ_buf.buf,
+				   ME6000_AO_CIRC_BUF_SIZE_ORDER);
+			subdevice->circ_buf.buf = NULL;
+			kfree(subdevice);
+			return NULL;
+		}
+		PINFO("Registered irq=%d.\n", subdevice->irq);
+	} else {
+		subdevice->irq = 0;
+	}
+
+	/* Initialize registers */
+	// Only streamed subdevices support interrupts. For the rest this register has no meaning.
+	subdevice->irq_status_reg = reg_base + ME6000_AO_IRQ_STATUS_REG;
+	subdevice->preload_reg = reg_base + ME6000_AO_PRELOAD_REG;
+
+	if (ao_idx == 0) {
+		subdevice->ctrl_reg = reg_base + ME6000_AO_00_CTRL_REG;
+		subdevice->status_reg = reg_base + ME6000_AO_00_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME6000_AO_00_FIFO_REG;
+		subdevice->timer_reg = reg_base + ME6000_AO_00_TIMER_REG;
+		subdevice->irq_reset_reg =
+		    reg_base + ME6000_AO_00_IRQ_RESET_REG;
+		subdevice->single_reg = reg_base + ME6000_AO_00_SINGLE_REG;
+	} else if (ao_idx == 1) {
+		subdevice->ctrl_reg = reg_base + ME6000_AO_01_CTRL_REG;
+		subdevice->status_reg = reg_base + ME6000_AO_01_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME6000_AO_01_FIFO_REG;
+		subdevice->timer_reg = reg_base + ME6000_AO_01_TIMER_REG;
+		subdevice->irq_reset_reg =
+		    reg_base + ME6000_AO_01_IRQ_RESET_REG;
+		subdevice->single_reg = reg_base + ME6000_AO_01_SINGLE_REG;
+	} else if (ao_idx == 2) {
+		subdevice->ctrl_reg = reg_base + ME6000_AO_02_CTRL_REG;
+		subdevice->status_reg = reg_base + ME6000_AO_02_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME6000_AO_02_FIFO_REG;
+		subdevice->timer_reg = reg_base + ME6000_AO_02_TIMER_REG;
+		subdevice->irq_reset_reg =
+		    reg_base + ME6000_AO_02_IRQ_RESET_REG;
+		subdevice->single_reg = reg_base + ME6000_AO_02_SINGLE_REG;
+	} else if (ao_idx == 3) {
+		subdevice->ctrl_reg = reg_base + ME6000_AO_03_CTRL_REG;
+		subdevice->status_reg = reg_base + ME6000_AO_03_STATUS_REG;
+		subdevice->fifo_reg = reg_base + ME6000_AO_03_FIFO_REG;
+		subdevice->timer_reg = reg_base + ME6000_AO_03_TIMER_REG;
+		subdevice->irq_reset_reg =
+		    reg_base + ME6000_AO_03_IRQ_RESET_REG;
+		subdevice->single_reg = reg_base + ME6000_AO_03_SINGLE_REG;
+	} else {
+		subdevice->ctrl_reg = reg_base + ME6000_AO_DUMY;
+		subdevice->fifo_reg = reg_base + ME6000_AO_DUMY;
+		subdevice->timer_reg = reg_base + ME6000_AO_DUMY;
+		subdevice->irq_reset_reg = reg_base + ME6000_AO_DUMY;
+		subdevice->single_reg = reg_base + ME6000_AO_DUMY;
+
+		subdevice->status_reg = reg_base + ME6000_AO_SINGLE_STATUS_REG;
+		if (ao_idx == 4) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_04_SINGLE_REG;
+		} else if (ao_idx == 5) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_05_SINGLE_REG;
+		} else if (ao_idx == 6) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_06_SINGLE_REG;
+		} else if (ao_idx == 7) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_07_SINGLE_REG;
+		} else if (ao_idx == 8) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_08_SINGLE_REG;
+		} else if (ao_idx == 9) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_09_SINGLE_REG;
+		} else if (ao_idx == 10) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_10_SINGLE_REG;
+		} else if (ao_idx == 11) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_11_SINGLE_REG;
+		} else if (ao_idx == 12) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_12_SINGLE_REG;
+		} else if (ao_idx == 13) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_13_SINGLE_REG;
+		} else if (ao_idx == 14) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_14_SINGLE_REG;
+		} else if (ao_idx == 15) {
+			subdevice->single_reg =
+			    reg_base + ME6000_AO_15_SINGLE_REG;
+		} else {
+			PERROR_CRITICAL("WRONG SUBDEVICE ID=%d!", ao_idx);
+			me_subdevice_deinit((me_subdevice_t *) subdevice);
+			if (subdevice->fifo) {
+				free_pages((unsigned long)subdevice->circ_buf.
+					   buf, ME6000_AO_CIRC_BUF_SIZE_ORDER);
+			}
+			subdevice->circ_buf.buf = NULL;
+			kfree(subdevice);
+			return NULL;
+		}
+	}
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Override base class methods. */
+	subdevice->base.me_subdevice_destructor = me6000_ao_destructor;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me6000_ao_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me6000_ao_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me6000_ao_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me6000_ao_io_single_write;
+	subdevice->base.me_subdevice_io_stream_config =
+	    me6000_ao_io_stream_config;
+	subdevice->base.me_subdevice_io_stream_new_values =
+	    me6000_ao_io_stream_new_values;
+	subdevice->base.me_subdevice_io_stream_write =
+	    me6000_ao_io_stream_write;
+	subdevice->base.me_subdevice_io_stream_start =
+	    me6000_ao_io_stream_start;
+	subdevice->base.me_subdevice_io_stream_status =
+	    me6000_ao_io_stream_status;
+	subdevice->base.me_subdevice_io_stream_stop = me6000_ao_io_stream_stop;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me6000_ao_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me6000_ao_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me6000_ao_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me6000_ao_query_subdevice_caps_args;
+	subdevice->base.me_subdevice_query_range_by_min_max =
+	    me6000_ao_query_range_by_min_max;
+	subdevice->base.me_subdevice_query_number_ranges =
+	    me6000_ao_query_number_ranges;
+	subdevice->base.me_subdevice_query_range_info =
+	    me6000_ao_query_range_info;
+	subdevice->base.me_subdevice_query_timer = me6000_ao_query_timer;
+
+	//prepare work queue and work function
+	subdevice->me6000_workqueue = me6000_wq;
+
+/* workqueue API changed in kernel 2.6.20 */
+#if ( LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) )
+	INIT_WORK(&subdevice->ao_control_task, me6000_ao_work_control_task,
+		  (void *)subdevice);
+#else
+	INIT_DELAYED_WORK(&subdevice->ao_control_task,
+			  me6000_ao_work_control_task);
+#endif
+
+	if (subdevice->fifo) {	//Set speed
+		outl(ME6000_AO_MIN_CHAN_TICKS - 1, subdevice->timer_reg);
+		subdevice->hardware_stop_delay = HZ / 10;	//100ms
+	}
+
+	return subdevice;
+}
+
+/** @brief Stop presentation. Preserve FIFOs.
+*
+* @param instance The subdevice instance (pointer).
+*/
+int inline ao_stop_immediately(me6000_ao_subdevice_t * instance)
+{
+	unsigned long cpu_flags;
+	uint32_t ctrl;
+	int timeout;
+	int i;
+	uint32_t single_mask;
+
+	single_mask =
+	    (instance->ao_idx - ME6000_AO_SINGLE_STATUS_OFFSET <
+	     0) ? 0x0000 : (0x0001 << (instance->ao_idx -
+				       ME6000_AO_SINGLE_STATUS_OFFSET));
+
+	timeout =
+	    (instance->hardware_stop_delay >
+	     (HZ / 10)) ? instance->hardware_stop_delay : HZ / 10;
+	for (i = 0; i <= timeout; i++) {
+		if (instance->fifo) {
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			// Stop all actions. No conditions! Block interrupts. Leave FIFO untouched!
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME6000_AO_CTRL_BIT_STOP |
+			    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP;
+			ctrl &=
+			    ~(ME6000_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			if (!(inl(instance->status_reg) & ME6000_AO_STATUS_BIT_FSM)) {	// Exit.
+				break;
+			}
+		} else {
+			if (!(inl(instance->status_reg) & single_mask)) {	// Exit.
+				break;
+			}
+		}
+
+		PINFO("<%s> Wait for stop: %d\n", __FUNCTION__, i);
+
+		//Still working!
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	if (i > timeout) {
+		PERROR_CRITICAL("FSM IS BUSY!\n");
+		return ME_ERRNO_INTERNAL;
+	}
+	return ME_ERRNO_SUCCESS;
+}
+
+/** @brief Copy data from circular buffer to fifo (fast) in wraparound.
+* @note This is time critical function. Checking is done at begining and end only.
+* @note The is not reasonable way to check how many walues was in FIFO at begining. The count must be managed externaly.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied data.
+* @return On error/success: 0.	No datas were copied => no data in buffer.
+* @return On error: -ME_ERRNO_FIFO_BUFFER_OVERFLOW.
+*/
+int inline ao_write_data_wraparound(me6000_ao_subdevice_t * instance, int count,
+				    int start_pos)
+{				/// @note This is time critical function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int i = 1;
+
+	if (count <= 0) {	//Wrong count!
+		return 0;
+	}
+
+	while (i < local_count) {
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+		if (pos == instance->circ_buf.head) {
+			pos = instance->circ_buf.tail;
+		}
+		i++;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME6000_AO_STATUS_BIT_FF)) {	//FIFO is full before all datas were copied!
+		PERROR("idx=%d FIFO is full before all datas were copied!\n",
+		       instance->ao_idx);
+		return -ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+	} else {		//Add last value
+		value = *(instance->circ_buf.buf + pos);
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+	}
+
+	PINFO("idx=%d WRAPAROUND LOADED %d values\n", instance->ao_idx,
+	      local_count);
+	return local_count;
+}
+
+/** @brief Copy data from software buffer to fifo (fast).
+* @note This is time critical function. Checking is done at begining and end only.
+* @note The is not reasonable way to check how many walues was in FIFO at begining. The count must be managed externaly.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied data.
+* @return On error/success: 0.	No datas were copied => no data in buffer.
+* @return On error: -ME_ERRNO_FIFO_BUFFER_OVERFLOW.
+*/
+int inline ao_write_data(me6000_ao_subdevice_t * instance, int count,
+			 int start_pos)
+{				/// @note This is time critical function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int max_count;
+	int i = 1;
+
+	if (count <= 0) {	//Wrong count!
+		return 0;
+	}
+
+	max_count = me_circ_buf_values(&instance->circ_buf) - start_pos;
+	if (max_count <= 0) {	//No data to copy!
+		return 0;
+	}
+
+	if (max_count < count) {
+		local_count = max_count;
+	}
+
+	while (i < local_count) {
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+		i++;
+	}
+
+	status = inl(instance->status_reg);
+	if (!(status & ME6000_AO_STATUS_BIT_FF)) {	//FIFO is full before all datas were copied!
+		PERROR("idx=%d FIFO is full before all datas were copied!\n",
+		       instance->ao_idx);
+		return -ME_ERRNO_FIFO_BUFFER_OVERFLOW;
+	} else {		//Add last value
+		value = *(instance->circ_buf.buf + pos);
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+	}
+
+	PINFO("idx=%d FAST LOADED %d values\n", instance->ao_idx, local_count);
+	return local_count;
+}
+
+/** @brief Copy data from software buffer to fifo (slow).
+* @note This is slow function that copy all data from buffer to FIFO with full control.
+*
+* @param instance The subdevice instance (pointer).
+* @param count Maximum number of copied data.
+* @param start_pos Position of the firs value in buffer.
+*
+* @return On success: Number of copied values.
+* @return On error/success: 0.	FIFO was full at begining.
+* @return On error: -ME_ERRNO_RING_BUFFER_UNDEFFLOW.
+*/
+int inline ao_write_data_pooling(me6000_ao_subdevice_t * instance, int count,
+				 int start_pos)
+{				/// @note This is slow function!
+	uint32_t status;
+	uint32_t value;
+	int pos =
+	    (instance->circ_buf.tail + start_pos) & instance->circ_buf.mask;
+	int local_count = count;
+	int i;
+	int max_count;
+
+	if (count <= 0) {	//Wrong count!
+		PERROR("idx=%d SLOW LOADED: Wrong count!\n", instance->ao_idx);
+		return 0;
+	}
+
+	max_count = me_circ_buf_values(&instance->circ_buf) - start_pos;
+	if (max_count <= 0) {	//No data to copy!
+		PERROR("idx=%d SLOW LOADED: No data to copy!\n",
+		       instance->ao_idx);
+		return 0;
+	}
+
+	if (max_count < count) {
+		local_count = max_count;
+	}
+
+	for (i = 0; i < local_count; i++) {
+		status = inl(instance->status_reg);
+		if (!(status & ME6000_AO_STATUS_BIT_FF)) {	//FIFO is full!
+			return i;
+		}
+		//Get value from buffer
+		value = *(instance->circ_buf.buf + pos);
+		//Prepare it
+		if (instance->ao_idx & 0x1) {
+			value <<= 16;
+		}
+		//Put value to FIFO
+		outl(value, instance->fifo_reg);
+		//PDEBUG_REG("idx=%d fifo_reg outl(0x%lX+0x%lX)=0x%x\n", instance->ao_idx, instance->reg_base, instance->fifo_reg - instance->reg_base, value);
+
+		pos++;
+		pos &= instance->circ_buf.mask;
+	}
+
+	PINFO("idx=%d SLOW LOADED %d values\n", instance->ao_idx, local_count);
+	return local_count;
+}
+
+/** @brief Copy data from user space to circular buffer.
+* @param instance The subdevice instance (pointer).
+* @param count Number of datas in user space.
+* @param user_values Buffer's pointer.
+*
+* @return On success: Number of copied values.
+* @return On error: -ME_ERRNO_INTERNAL.
+*/
+int inline ao_get_data_from_user(me6000_ao_subdevice_t * instance, int count,
+				 int *user_values)
+{
+	int i, err;
+	int empty_space;
+	int copied;
+	int value;
+
+	empty_space = me_circ_buf_space(&instance->circ_buf);
+	//We have only this space free.
+	copied = (count < empty_space) ? count : empty_space;
+	for (i = 0; i < copied; i++) {	//Copy from user to buffer
+		if ((err = get_user(value, (int *)(user_values + i)))) {
+			PERROR
+			    ("idx=%d BUFFER LOADED: get_user(0x%p) return an error: %d\n",
+			     instance->ao_idx, user_values + i, err);
+			return -ME_ERRNO_INTERNAL;
+		}
+		/// @note The analog output in me6000 series has size of 16 bits.
+		*(instance->circ_buf.buf + instance->circ_buf.head) =
+		    (uint16_t) value;
+		instance->circ_buf.head++;
+		instance->circ_buf.head &= instance->circ_buf.mask;
+	}
+
+	PINFO("idx=%d BUFFER LOADED %d values\n", instance->ao_idx, copied);
+	return copied;
+}
+
+static void me6000_ao_work_control_task(
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+					       void *subdevice
+#else
+					       struct work_struct *work
+#endif
+    )
+{
+	me6000_ao_subdevice_t *instance;
+	unsigned long cpu_flags = 0;
+	uint32_t status;
+	uint32_t ctrl;
+	uint32_t synch;
+	int reschedule = 0;
+	int signaling = 0;
+	uint32_t single_mask;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	instance = (me6000_ao_subdevice_t *) subdevice;
+#else
+	instance =
+	    container_of((void *)work, me6000_ao_subdevice_t, ao_control_task);
+#endif
+	PINFO("<%s: %ld> executed. idx=%d\n", __FUNCTION__, jiffies,
+	      instance->ao_idx);
+
+	status = inl(instance->status_reg);
+	PDEBUG_REG("status_reg inl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->status_reg - instance->reg_base, status);
+
+/// @note AO_STATUS_BIT_FSM doesn't work as should be for pure single channels (idx>=4)
+//      single_mask = (instance->ao_idx-ME6000_AO_SINGLE_STATUS_OFFSET < 0) ? 0x0000 : (0x0001 << (instance->ao_idx-ME6000_AO_SINGLE_STATUS_OFFSET));
+	single_mask = *instance->triggering_flags & (0x1 << instance->ao_idx);
+
+	switch (instance->status) {	// Checking actual mode.
+
+		// Not configured for work.
+	case ao_status_none:
+		break;
+
+		//This are stable modes. No need to do anything. (?)
+	case ao_status_single_configured:
+	case ao_status_stream_configured:
+	case ao_status_stream_fifo_error:
+	case ao_status_stream_buffer_error:
+	case ao_status_stream_error:
+		PERROR("Shouldn't be running!.\n");
+		break;
+
+		// Single modes
+	case ao_status_single_run_wait:
+	case ao_status_single_run:
+	case ao_status_single_end_wait:
+		if (instance->fifo) {	// Extra registers.
+			if (!(status & ME6000_AO_STATUS_BIT_FSM)) {	// State machine is not working.
+				if (((instance->fifo & ME6000_AO_HAS_FIFO)
+				     && (!(status & ME6000_AO_STATUS_BIT_EF)))
+				    || (!(instance->fifo & ME6000_AO_HAS_FIFO))) {	// Single is in end state.
+					PDEBUG
+					    ("Single call has been complited.\n");
+
+					// Set correct value for single_read();
+					instance->single_value =
+					    instance->single_value_in_fifo;
+
+					// Set status as 'ao_status_single_end'
+					instance->status = ao_status_single_end;
+
+					spin_lock(instance->preload_reg_lock);
+					if ((single_mask) && (*instance->preload_flags & (ME6000_AO_SYNC_HOLD << instance->ao_idx))) {	// This is one of synchronous start channels. Set all as triggered.
+						*instance->triggering_flags =
+						    0x00000000;
+					} else {
+						//Set this channel as triggered (none active).
+						*instance->triggering_flags &=
+						    ~(0x1 << instance->ao_idx);
+					}
+					spin_unlock(instance->preload_reg_lock);
+
+					// Signal the end.
+					signaling = 1;
+					// Wait for stop ISM.
+					reschedule = 1;
+
+					break;
+				}
+			}
+			// Check timeout.
+			if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+				PDEBUG("Timeout reached.\n");
+				// Stop all actions. No conditions! Block interrupts and trigger. Leave FIFO untouched!
+				spin_lock_irqsave(&instance->subdevice_lock,
+						  cpu_flags);
+				ctrl = inl(instance->ctrl_reg);
+				ctrl |=
+				    ME6000_AO_CTRL_BIT_STOP |
+				    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP;
+				ctrl &=
+				    ~(ME6000_AO_CTRL_BIT_ENABLE_IRQ |
+				      ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG);
+				ctrl &=
+				    ~(ME6000_AO_CTRL_BIT_EX_TRIG_EDGE |
+				      ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH);
+				//Disabling FIFO
+				ctrl &= ~ME6000_AO_CTRL_BIT_ENABLE_FIFO;
+
+				outl(ctrl, instance->ctrl_reg);
+				PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->ctrl_reg -
+					   instance->reg_base, ctrl);
+				spin_unlock_irqrestore(&instance->
+						       subdevice_lock,
+						       cpu_flags);
+
+				//Reset interrupt latch
+				inl(instance->irq_reset_reg);
+
+				spin_lock(instance->preload_reg_lock);
+				//Remove from synchronous start. Block triggering from this output.
+				synch = inl(instance->preload_reg);
+				synch &=
+				    ~((ME6000_AO_SYNC_HOLD |
+				       ME6000_AO_SYNC_EXT_TRIG) << instance->
+				      ao_idx);
+				if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {	// No FIFO - set to single safe mode
+					synch |=
+					    ME6000_AO_SYNC_HOLD << instance->
+					    ao_idx;
+				}
+				outl(synch, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     synch);
+				//Set this channel as triggered (none active).
+				*instance->triggering_flags &=
+				    ~(0x1 << instance->ao_idx);
+				spin_unlock(instance->preload_reg_lock);
+
+				// Set correct value for single_read();
+				instance->single_value_in_fifo =
+				    instance->single_value;
+
+				instance->status = ao_status_single_end;
+
+				// Signal the end.
+				signaling = 1;
+			}
+		} else {	// No extra registers.
+/*
+				if (!(status & single_mask))
+				{// State machine is not working.
+					PDEBUG("Single call has been complited.\n");
+
+					// Set correct value for single_read();
+					instance->single_value = instance->single_value_in_fifo;
+
+					// Set status as 'ao_status_single_end'
+					instance->status = ao_status_single_end;
+
+					// Signal the end.
+					signaling = 1;
+					// Wait for stop ISM.
+					reschedule = 1;
+
+					break;
+				}
+*/
+			if (!single_mask) {	// Was triggered.
+				PDEBUG("Single call has been complited.\n");
+
+				// Set correct value for single_read();
+				instance->single_value =
+				    instance->single_value_in_fifo;
+
+				// Set status as 'ao_status_single_end'
+				instance->status = ao_status_single_end;
+
+				// Signal the end.
+				signaling = 1;
+
+				break;
+			}
+			// Check timeout.
+			if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+				PDEBUG("Timeout reached.\n");
+
+				spin_lock(instance->preload_reg_lock);
+				//Remove from synchronous start. Block triggering from this output.
+				synch = inl(instance->preload_reg);
+				synch &=
+				    ~(ME6000_AO_SYNC_EXT_TRIG << instance->
+				      ao_idx);
+				synch |=
+				    ME6000_AO_SYNC_HOLD << instance->ao_idx;
+
+				outl(synch, instance->preload_reg);
+				PDEBUG_REG
+				    ("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->preload_reg - instance->reg_base,
+				     synch);
+				//Set this channel as triggered (none active).
+				*instance->triggering_flags &=
+				    ~(0x1 << instance->ao_idx);
+				spin_unlock(instance->preload_reg_lock);
+
+				// Restore old settings.
+				PDEBUG("Write old value back to register.\n");
+				outl(instance->single_value,
+				     instance->single_reg);
+				PDEBUG_REG
+				    ("single_reg outl(0x%lX+0x%lX)=0x%x\n",
+				     instance->reg_base,
+				     instance->single_reg - instance->reg_base,
+				     instance->single_value);
+
+				// Set correct value for single_read();
+				instance->single_value_in_fifo =
+				    instance->single_value;
+
+				instance->status = ao_status_single_end;
+
+				// Signal the end.
+				signaling = 1;
+			}
+		}
+
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+	case ao_status_stream_end:
+		if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {	// No FIFO
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+	case ao_status_single_end:
+		if (instance->fifo) {	// Extra registers.
+			if (status & ME6000_AO_STATUS_BIT_FSM) {	// State machine is working but the status is set to end. Force stop.
+
+				// Wait for stop.
+				reschedule = 1;
+			}
+
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			// Stop all actions. No conditions! Block interrupts and trigger. Leave FIFO untouched!
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP |
+			    ME6000_AO_CTRL_BIT_STOP;
+			ctrl &=
+			    ~(ME6000_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			//Reset interrupt latch
+			inl(instance->irq_reset_reg);
+		} else {	// No extra registers.
+/*
+				if (status & single_mask)
+				{// State machine is working but the status is set to end. Force stop.
+
+					// Wait for stop.
+					reschedule = 1;
+				}
+*/
+		}
+		break;
+
+		// Stream modes
+	case ao_status_stream_run_wait:
+		if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {	// No FIFO
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (status & ME6000_AO_STATUS_BIT_FSM) {	// State machine is working. Waiting for start finish.
+			instance->status = ao_status_stream_run;
+
+			// Signal end of this step
+			signaling = 1;
+		} else {	// State machine is not working.
+			if (!(status & ME6000_AO_STATUS_BIT_EF)) {	// FIFO is empty. Procedure has started and finish already!
+				instance->status = ao_status_stream_end;
+
+				// Signal the end.
+				signaling = 1;
+				// Wait for stop.
+				reschedule = 1;
+				break;
+			}
+		}
+
+		// Check timeout.
+		if ((instance->timeout.delay) && ((jiffies - instance->timeout.start_time) >= instance->timeout.delay)) {	// Timeout
+			PDEBUG("Timeout reached.\n");
+			// Stop all actions. No conditions! Block interrupts. Leave FIFO untouched!
+			spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+			ctrl = inl(instance->ctrl_reg);
+			ctrl |=
+			    ME6000_AO_CTRL_BIT_STOP |
+			    ME6000_AO_CTRL_BIT_IMMEDIATE_STOP;
+			ctrl &=
+			    ~(ME6000_AO_CTRL_BIT_ENABLE_IRQ |
+			      ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG);
+			outl(ctrl, instance->ctrl_reg);
+			PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->ctrl_reg - instance->reg_base,
+				   ctrl);
+			spin_unlock_irqrestore(&instance->subdevice_lock,
+					       cpu_flags);
+
+			//Reset interrupt latch
+			inl(instance->irq_reset_reg);
+
+			spin_lock(instance->preload_reg_lock);
+			//Remove from synchronous start. Block triggering from this output.
+			synch = inl(instance->preload_reg);
+			synch &=
+			    ~((ME6000_AO_SYNC_HOLD | ME6000_AO_SYNC_EXT_TRIG) <<
+			      instance->ao_idx);
+			outl(synch, instance->preload_reg);
+			PDEBUG_REG("preload_reg outl(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->preload_reg - instance->reg_base,
+				   synch);
+			spin_unlock(instance->preload_reg_lock);
+
+			instance->status = ao_status_stream_end;
+
+			// Signal the end.
+			signaling = 1;
+		}
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+	case ao_status_stream_run:
+		if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {	// No FIFO
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (!(status & ME6000_AO_STATUS_BIT_FSM)) {	// State machine is not working. This is an error.
+			// BROKEN PIPE!
+			if (!(status & ME6000_AO_STATUS_BIT_EF)) {	// FIFO is empty.
+				if (me_circ_buf_values(&instance->circ_buf)) {	// Software buffer is not empty.
+					if (instance->stop_data_count && (instance->stop_data_count <= instance->data_count)) {	//Finishing work. Requed data shown.
+						PDEBUG
+						    ("ISM stoped. No data in FIFO. Buffer is not empty.\n");
+						instance->status =
+						    ao_status_stream_end;
+					} else {
+						PERROR
+						    ("Output stream has been broken. ISM stoped. No data in FIFO. Buffer is not empty.\n");
+						instance->status =
+						    ao_status_stream_buffer_error;
+					}
+				} else {	// Software buffer is empty.
+					PDEBUG
+					    ("ISM stoped. No data in FIFO. Buffer is empty.\n");
+					instance->status = ao_status_stream_end;
+				}
+			} else {	// There are still datas in FIFO.
+				if (me_circ_buf_values(&instance->circ_buf)) {	// Software buffer is not empty.
+					PERROR
+					    ("Output stream has been broken. ISM stoped but some data in FIFO and buffer.\n");
+				} else {	// Software buffer is empty.
+					PERROR
+					    ("Output stream has been broken. ISM stoped but some data in FIFO. Buffer is empty.\n");
+				}
+				instance->status = ao_status_stream_fifo_error;
+
+			}
+
+			// Signal the failure.
+			signaling = 1;
+			break;
+		}
+		// Wait for stop.
+		reschedule = 1;
+		break;
+
+	case ao_status_stream_end_wait:
+		if (!(instance->fifo & ME6000_AO_HAS_FIFO)) {	// No FIFO
+			PERROR_CRITICAL
+			    ("Streaming on single device! This feature is not implemented in this version!\n");
+			instance->status = ao_status_stream_error;
+			// Signal the end.
+			signaling = 1;
+			break;
+		}
+
+		if (!(status & ME6000_AO_STATUS_BIT_FSM)) {	// State machine is not working. Waiting for stop finish.
+			instance->status = ao_status_stream_end;
+			signaling = 1;
+		}
+		// State machine is working.
+		reschedule = 1;
+		break;
+
+	default:
+		PERROR_CRITICAL("Status is in wrong state (%d)!\n",
+				instance->status);
+		instance->status = ao_status_stream_error;
+		// Signal the end.
+		signaling = 1;
+		break;
+
+	}
+
+	if (signaling) {	//Signal it.
+		wake_up_interruptible_all(&instance->wait_queue);
+	}
+
+	if (instance->ao_control_task_flag && reschedule) {	// Reschedule task
+		queue_delayed_work(instance->me6000_workqueue,
+				   &instance->ao_control_task, 1);
+	} else {
+		PINFO("<%s> Ending control task.\n", __FUNCTION__);
+	}
+
+}
+
+static int me6000_ao_query_range_by_min_max(me_subdevice_t * subdevice,
+					    int unit,
+					    int *min,
+					    int *max, int *maxdata, int *range)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((*max - *min) < 0) {
+		PERROR("Invalid minimum and maximum values specified.\n");
+		return ME_ERRNO_INVALID_MIN_MAX;
+	}
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		if ((*max <= (instance->max + 1000)) && (*min >= instance->min)) {
+			*min = instance->min;
+			*max = instance->max;
+			*maxdata = ME6000_AO_MAX_DATA;
+			*range = 0;
+		} else {
+			PERROR("No matching range available.\n");
+			return ME_ERRNO_NO_RANGE;
+		}
+	} else {
+		PERROR("Invalid physical unit specified.\n");
+		return ME_ERRNO_INVALID_UNIT;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_number_ranges(me_subdevice_t * subdevice,
+					 int unit, int *count)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if ((unit == ME_UNIT_VOLT) || (unit == ME_UNIT_ANY)) {
+		*count = 1;
+	} else {
+		*count = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_range_info(me_subdevice_t * subdevice,
+				      int range,
+				      int *unit,
+				      int *min, int *max, int *maxdata)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (range == 0) {
+		*unit = ME_UNIT_VOLT;
+		*min = instance->min;
+		*max = instance->max;
+		*maxdata = ME6000_AO_MAX_DATA;
+	} else {
+		PERROR("Invalid range number specified.\n");
+		return ME_ERRNO_INVALID_RANGE;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_timer(me_subdevice_t * subdevice,
+				 int timer,
+				 int *base_frequency,
+				 long long *min_ticks, long long *max_ticks)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (instance->fifo) {	//Streaming device.
+		*base_frequency = ME6000_AO_BASE_FREQUENCY;
+		if (timer == ME_TIMER_ACQ_START) {
+			*min_ticks = ME6000_AO_MIN_ACQ_TICKS;
+			*max_ticks = ME6000_AO_MAX_ACQ_TICKS;
+		} else if (timer == ME_TIMER_CONV_START) {
+			*min_ticks = ME6000_AO_MIN_CHAN_TICKS;
+			*max_ticks = ME6000_AO_MAX_CHAN_TICKS;
+		}
+	} else {		//Not streaming device!
+		*base_frequency = 0;
+		*min_ticks = 0;
+		*max_ticks = 0;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	me6000_ao_subdevice_t *instance;
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*number = 1;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	me6000_ao_subdevice_t *instance;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*type = ME_TYPE_AO;
+	*subtype =
+	    (instance->
+	     fifo & ME6000_AO_HAS_FIFO) ? ME_SUBTYPE_STREAMING :
+	    ME_SUBTYPE_SINGLE;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	me6000_ao_subdevice_t *instance;
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	*caps =
+	    ME_CAPS_AO_TRIG_SYNCHRONOUS | ((instance->fifo) ? ME_CAPS_AO_FIFO :
+					   ME_CAPS_NONE);
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_ao_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					       int cap, int *args, int count)
+{
+	me6000_ao_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	instance = (me6000_ao_subdevice_t *) subdevice;
+
+	PDEBUG("executed. idx=%d\n", instance->ao_idx);
+
+	if (count != 1) {
+		PERROR("Invalid capability argument count.\n");
+		return ME_ERRNO_INVALID_CAP_ARG_COUNT;
+	}
+
+	switch (cap) {
+	case ME_CAP_AI_FIFO_SIZE:
+		args[0] = (instance->fifo) ? ME6000_AO_FIFO_COUNT : 0;
+		break;
+
+	case ME_CAP_AI_BUFFER_SIZE:
+		args[0] =
+		    (instance->circ_buf.buf) ? ME6000_AO_CIRC_BUF_COUNT : 0;
+		break;
+
+	default:
+		PERROR("Invalid capability.\n");
+		err = ME_ERRNO_INVALID_CAP;
+		args[0] = 0;
+	}
+
+	return err;
+}

+ 200 - 0
drivers/staging/meilhaus/me6000_ao.h

@@ -0,0 +1,200 @@
+/**
+ * @file me6000_ao.h
+ *
+ * @brief Meilhaus ME-6000 analog output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_AO_H_
+#define _ME6000_AO_H_
+
+#include <linux/version.h>
+#include "mesubdevice.h"
+#include "mecirc_buf.h"
+#include "meioctl.h"
+
+#ifdef __KERNEL__
+
+#define ME6000_AO_MAX_SUBDEVICES	16
+#define ME6000_AO_FIFO_COUNT		8192
+
+#define ME6000_AO_BASE_FREQUENCY	33000000L
+
+#define ME6000_AO_MIN_ACQ_TICKS		0LL
+#define ME6000_AO_MAX_ACQ_TICKS		0LL
+
+#define ME6000_AO_MIN_CHAN_TICKS	66LL
+#define ME6000_AO_MAX_CHAN_TICKS	0xFFFFFFFFLL
+
+#define ME6000_AO_MIN_RANGE			-10000000
+#define ME6000_AO_MAX_RANGE			9999694
+
+#define ME6000_AO_MIN_RANGE_HIGH	0
+#define ME6000_AO_MAX_RANGE_HIGH	49999237
+
+#define ME6000_AO_MAX_DATA			0xFFFF
+
+#ifdef ME_SYNAPSE
+# define ME6000_AO_CIRC_BUF_SIZE_ORDER 		8	// 2^n PAGES =>> Maximum value of 1MB for Synapse
+#else
+# define ME6000_AO_CIRC_BUF_SIZE_ORDER 		5	// 2^n PAGES =>> 128KB
+#endif
+#define ME6000_AO_CIRC_BUF_SIZE 		PAGE_SIZE<<ME6000_AO_CIRC_BUF_SIZE_ORDER	// Buffer size in bytes.
+
+#  ifdef _CBUFF_32b_t
+#   define ME6000_AO_CIRC_BUF_COUNT	((ME6000_AO_CIRC_BUF_SIZE) / sizeof(uint32_t))	// Size in values
+#  else
+#   define ME6000_AO_CIRC_BUF_COUNT	((ME6000_AO_CIRC_BUF_SIZE) / sizeof(uint16_t))	// Size in values
+#  endif
+
+#  define ME6000_AO_CONTINOUS					0x0
+#  define ME6000_AO_WRAP_MODE					0x1
+#  define ME6000_AO_HW_MODE						0x2
+
+#  define ME6000_AO_HW_WRAP_MODE				(ME6000_AO_WRAP_MODE | ME6000_AO_HW_MODE)
+#  define ME6000_AO_SW_WRAP_MODE				ME6000_AO_WRAP_MODE
+
+#  define ME6000_AO_INF_STOP_MODE				0x0
+#  define ME6000_AO_ACQ_STOP_MODE				0x1
+#  define ME6000_AO_SCAN_STOP_MODE				0x2
+
+#  define ME6000_AO_EXTRA_HARDWARE				0x1
+#  define ME6000_AO_HAS_FIFO					0x2
+
+typedef enum ME6000_AO_STATUS {
+	ao_status_none = 0,
+	ao_status_single_configured,
+	ao_status_single_run_wait,
+	ao_status_single_run,
+	ao_status_single_end_wait,
+	ao_status_single_end,
+	ao_status_stream_configured,
+	ao_status_stream_run_wait,
+	ao_status_stream_run,
+	ao_status_stream_end_wait,
+	ao_status_stream_end,
+	ao_status_stream_fifo_error,
+	ao_status_stream_buffer_error,
+	ao_status_stream_error,
+	ao_status_last
+} ME6000_AO_STATUS;
+
+typedef struct me6000_ao_timeout {
+	unsigned long start_time;
+	unsigned long delay;
+} me6000_ao_timeout_t;
+
+/**
+ * @brief The ME-6000 analog output subdevice class.
+ */
+typedef struct me6000_ao_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;					/**< The subdevice base class. */
+	unsigned int ao_idx;
+
+	/* Attributes */
+	spinlock_t subdevice_lock;				/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *preload_reg_lock;			/**< Spin lock to protect preload_reg from concurrent access. */
+
+	uint32_t *preload_flags;
+	uint32_t *triggering_flags;
+
+	/* Hardware feautres */
+	unsigned int irq;						/**< The interrupt request number assigned by the PCI BIOS. */
+	int fifo;								/**< If set this device has a FIFO. */
+
+	//Range
+	int min;
+	int max;
+
+	int single_value;						/**< Mirror of the output value in single mode. */
+	int single_value_in_fifo;				/**< Mirror of the value written in single mode. */
+	uint32_t ctrl_trg;						/**< Mirror of the trigger settings. */
+
+	volatile int mode;						/**< Flags used for storing SW wraparound setup*/
+	int stop_mode;							/**< The user defined stop condition flag. */
+	unsigned int start_mode;
+	unsigned int stop_count;				/**< The user defined dates presentation end count. */
+	unsigned int stop_data_count;			/**< The stop presentation count. */
+	unsigned int data_count;				/**< The real presentation count. */
+	unsigned int preloaded_count;			/**< The next data addres in buffer. <= for wraparound mode. */
+	int hardware_stop_delay;				/**< The time that stop can take. This is only to not show hardware bug to user. */
+
+	volatile enum ME6000_AO_STATUS status;	/**< The current stream status flag. */
+	me6000_ao_timeout_t timeout;			/**< The timeout for start in blocking and non-blocking mode. */
+
+									/* Registers *//**< All registers are 32 bits long. */
+	unsigned long ctrl_reg;
+	unsigned long status_reg;
+	unsigned long fifo_reg;
+	unsigned long single_reg;
+	unsigned long timer_reg;
+	unsigned long irq_status_reg;
+	unsigned long preload_reg;
+	unsigned long irq_reset_reg;
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+
+	/* Software buffer */
+	me_circ_buf_t circ_buf;					/**< Circular buffer holding measurment data. */
+	wait_queue_head_t wait_queue;			/**< Wait queue to put on tasks waiting for data to arrive. */
+
+	struct workqueue_struct *me6000_workqueue;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+	struct work_struct ao_control_task;
+#else
+	struct delayed_work ao_control_task;
+#endif
+
+	volatile int ao_control_task_flag;		/**< Flag controling reexecuting of control task */
+
+} me6000_ao_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-6000 analog output subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the control register from concurrent access.
+ * @param preload_flags Pointer to spin lock protecting the hold&trigger register from concurrent access.
+ * @param ao_idx Subdevice number.
+ * @param fifo Flag set if subdevice has hardware FIFO.
+ * @param irq IRQ number.
+ * @param high_range Flag set if subdevice has high curren output.
+ * @param me6000_wq Queue for asynchronous task (1 queue for all subdevice on 1 board).
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me6000_ao_subdevice_t *me6000_ao_constructor(uint32_t reg_base,
+					     spinlock_t * preload_reg_lock,
+					     uint32_t * preload_flags,
+					     uint32_t * triggering_flags,
+					     int ao_idx,
+					     int fifo,
+					     int irq,
+					     int high_range,
+					     struct workqueue_struct
+					     *me6000_wq);
+
+#endif //__KERNEL__
+#endif //_ME6000_AO_H_

+ 177 - 0
drivers/staging/meilhaus/me6000_ao_reg.h

@@ -0,0 +1,177 @@
+/**
+ * @file me6000_ao_reg.h
+ *
+ * @brief ME-6000 analog output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_AO_REG_H_
+#define _ME6000_AO_REG_H_
+
+#ifdef __KERNEL__
+
+// AO
+#define ME6000_AO_00_CTRL_REG					0x00	// R/W
+#define ME6000_AO_00_STATUS_REG					0x04	// R/_
+#define ME6000_AO_00_FIFO_REG					0x08	// _/W
+#define ME6000_AO_00_SINGLE_REG					0x0C	// R/W
+#define ME6000_AO_00_TIMER_REG					0x10	// _/W
+
+#define ME6000_AO_01_CTRL_REG					0x18	// R/W
+#define ME6000_AO_01_STATUS_REG					0x1C	// R/_
+#define ME6000_AO_01_FIFO_REG					0x20	// _/W
+#define ME6000_AO_01_SINGLE_REG					0x24	// R/W
+#define ME6000_AO_01_TIMER_REG					0x28	// _/W
+
+#define ME6000_AO_02_CTRL_REG					0x30	// R/W
+#define ME6000_AO_02_STATUS_REG					0x34	// R/_
+#define ME6000_AO_02_FIFO_REG					0x38	// _/W
+#define ME6000_AO_02_SINGLE_REG					0x3C	// R/W
+#define ME6000_AO_02_TIMER_REG					0x40	// _/W
+
+#define ME6000_AO_03_CTRL_REG					0x48	// R/W
+#define ME6000_AO_03_STATUS_REG					0x4C	// R/_
+#define ME6000_AO_03_FIFO_REG					0x50	// _/W
+#define ME6000_AO_03_SINGLE_REG					0x54	// R/W
+#define ME6000_AO_03_TIMER_REG					0x58	// _/W
+
+#define ME6000_AO_SINGLE_STATUS_REG				0xA4	// R/_
+#define ME6000_AO_SINGLE_STATUS_OFFSET			4	//The first single subdevice => bit 0 in ME6000_AO_SINGLE_STATUS_REG.
+
+#define ME6000_AO_04_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_04_SINGLE_REG					0x74	// _/W
+
+#define ME6000_AO_05_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_05_SINGLE_REG					0x78	// _/W
+
+#define ME6000_AO_06_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_06_SINGLE_REG					0x7C	// _/W
+
+#define ME6000_AO_07_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_07_SINGLE_REG					0x80	// _/W
+
+#define ME6000_AO_08_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_08_SINGLE_REG					0x84	// _/W
+
+#define ME6000_AO_09_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_09_SINGLE_REG					0x88	// _/W
+
+#define ME6000_AO_10_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_10_SINGLE_REG					0x8C	// _/W
+
+#define ME6000_AO_11_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_11_SINGLE_REG					0x90	// _/W
+
+#define ME6000_AO_12_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_12_SINGLE_REG					0x94	// _/W
+
+#define ME6000_AO_13_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_13_SINGLE_REG					0x98	// _/W
+
+#define ME6000_AO_14_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_14_SINGLE_REG					0x9C	// _/W
+
+#define ME6000_AO_15_STATUS_REG					ME6000_AO_SINGLE_STATUS_REG
+#define ME6000_AO_15_SINGLE_REG					0xA0	// _/W
+
+//ME6000_AO_CTRL_REG
+#define ME6000_AO_MODE_SINGLE					0x00
+#define ME6000_AO_MODE_WRAPAROUND				0x01
+#define ME6000_AO_MODE_CONTINUOUS				0x02
+#define ME6000_AO_CTRL_MODE_MASK				(ME6000_AO_MODE_WRAPAROUND | ME6000_AO_MODE_CONTINUOUS)
+
+#define ME6000_AO_CTRL_BIT_MODE_WRAPAROUND		0x001
+#define ME6000_AO_CTRL_BIT_MODE_CONTINUOUS		0x002
+#define ME6000_AO_CTRL_BIT_STOP					0x004
+#define ME6000_AO_CTRL_BIT_ENABLE_FIFO			0x008
+#define ME6000_AO_CTRL_BIT_ENABLE_EX_TRIG		0x010
+#define ME6000_AO_CTRL_BIT_EX_TRIG_EDGE			0x020
+#define ME6000_AO_CTRL_BIT_ENABLE_IRQ			0x040
+#define ME6000_AO_CTRL_BIT_IMMEDIATE_STOP		0x080
+#define ME6000_AO_CTRL_BIT_EX_TRIG_EDGE_BOTH 	0x800
+
+//ME6000_AO_STATUS_REG
+#define ME6000_AO_STATUS_BIT_FSM				0x01
+#define ME6000_AO_STATUS_BIT_FF					0x02
+#define ME6000_AO_STATUS_BIT_HF					0x04
+#define ME6000_AO_STATUS_BIT_EF					0x08
+
+#define ME6000_AO_PRELOAD_REG					0xA8	// R/W    ///ME6000_AO_SYNC_REG <==> ME6000_AO_PRELOAD_REG
+/*
+#define ME6000_AO_SYNC_HOLD_0					0x00000001
+#define ME6000_AO_SYNC_HOLD_1					0x00000002
+#define ME6000_AO_SYNC_HOLD_2					0x00000004
+#define ME6000_AO_SYNC_HOLD_3					0x00000008
+#define ME6000_AO_SYNC_HOLD_4					0x00000010
+#define ME6000_AO_SYNC_HOLD_5					0x00000020
+#define ME6000_AO_SYNC_HOLD_6					0x00000040
+#define ME6000_AO_SYNC_HOLD_7					0x00000080
+#define ME6000_AO_SYNC_HOLD_8					0x00000100
+#define ME6000_AO_SYNC_HOLD_9					0x00000200
+#define ME6000_AO_SYNC_HOLD_10					0x00000400
+#define ME6000_AO_SYNC_HOLD_11					0x00000800
+#define ME6000_AO_SYNC_HOLD_12					0x00001000
+#define ME6000_AO_SYNC_HOLD_13					0x00002000
+#define ME6000_AO_SYNC_HOLD_14					0x00004000
+#define ME6000_AO_SYNC_HOLD_15					0x00008000
+*/
+#define ME6000_AO_SYNC_HOLD						0x00000001
+/*
+#define ME6000_AO_SYNC_EXT_TRIG_0				0x00010000
+#define ME6000_AO_SYNC_EXT_TRIG_1				0x00020000
+#define ME6000_AO_SYNC_EXT_TRIG_2				0x00040000
+#define ME6000_AO_SYNC_EXT_TRIG_3				0x00080000
+#define ME6000_AO_SYNC_EXT_TRIG_4				0x00100000
+#define ME6000_AO_SYNC_EXT_TRIG_5				0x00200000
+#define ME6000_AO_SYNC_EXT_TRIG_6				0x00400000
+#define ME6000_AO_SYNC_EXT_TRIG_7				0x00800000
+#define ME6000_AO_SYNC_EXT_TRIG_8				0x01000000
+#define ME6000_AO_SYNC_EXT_TRIG_9				0x02000000
+#define ME6000_AO_SYNC_EXT_TRIG_10				0x04000000
+#define ME6000_AO_SYNC_EXT_TRIG_11				0x08000000
+#define ME6000_AO_SYNC_EXT_TRIG_12				0x10000000
+#define ME6000_AO_SYNC_EXT_TRIG_13				0x20000000
+#define ME6000_AO_SYNC_EXT_TRIG_14				0x40000000
+#define ME6000_AO_SYNC_EXT_TRIG_15				0x80000000
+*/
+#define ME6000_AO_SYNC_EXT_TRIG					0x00010000
+
+#define ME6000_AO_EXT_TRIG						0x80000000
+
+// AO-IRQ
+#define ME6000_AO_IRQ_STATUS_REG				0x60	// R/_
+#define ME6000_AO_00_IRQ_RESET_REG				0x64	// R/_
+#define ME6000_AO_01_IRQ_RESET_REG				0x68	// R/_
+#define ME6000_AO_02_IRQ_RESET_REG				0x6C	// R/_
+#define ME6000_AO_03_IRQ_RESET_REG				0x70	// R/_
+
+#define ME6000_IRQ_STATUS_BIT_0					0x01
+#define ME6000_IRQ_STATUS_BIT_1					0x02
+#define ME6000_IRQ_STATUS_BIT_2					0x04
+#define ME6000_IRQ_STATUS_BIT_3					0x08
+
+#define ME6000_IRQ_STATUS_BIT_AO_HF				ME6000_IRQ_STATUS_BIT_0
+
+//DUMY register
+#define ME6000_AO_DUMY									0xFC
+#endif
+#endif

+ 211 - 0
drivers/staging/meilhaus/me6000_device.c

@@ -0,0 +1,211 @@
+/**
+ * @file me6000_device.c
+ *
+ * @brief Device class template implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "mefirmware.h"
+
+#include "mesubdevice.h"
+#include "medebug.h"
+#include "medevice.h"
+#include "me6000_reg.h"
+#include "me6000_device.h"
+#include "meplx_reg.h"
+#include "me6000_dio.h"
+#include "me6000_ao.h"
+
+/**
+ * @brief Global variable.
+ * This is working queue for runing a separate atask that will be responsible for work status (start, stop, timeouts).
+ */
+static struct workqueue_struct *me6000_workqueue;
+
+me_device_t *me6000_pci_constructor(struct pci_dev *pci_device)
+{
+	me6000_device_t *me6000_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+	int high_range = 0;
+	int fifo;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me6000_device = kmalloc(sizeof(me6000_device_t), GFP_KERNEL);
+
+	if (!me6000_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me6000_device, 0, sizeof(me6000_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me6000_device, pci_device);
+
+	if (err) {
+		kfree(me6000_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Download the xilinx firmware */
+	err = me_xilinx_download(me6000_device->base.info.pci.reg_bases[1],
+				 me6000_device->base.info.pci.reg_bases[2],
+				 &pci_device->dev, "me6000.bin");
+
+	if (err) {
+		me_device_deinit((me_device_t *) me6000_device);
+		kfree(me6000_device);
+		PERROR("Can't download firmware.\n");
+		return NULL;
+	}
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me6000_versions_get_device_index(me6000_device->base.info.pci.
+					     device_id);
+
+	// Initialize spin lock .
+	spin_lock_init(&me6000_device->preload_reg_lock);
+	spin_lock_init(&me6000_device->dio_ctrl_reg_lock);
+
+	/* Create digital input/output instances. */
+	for (i = 0; i < me6000_versions[version_idx].dio_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me6000_dio_constructor(me6000_device->
+							      base.info.pci.
+							      reg_bases[3], i,
+							      &me6000_device->
+							      dio_ctrl_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me6000_device);
+			kfree(me6000_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me6000_device->base.slist,
+					    subdevice);
+	}
+
+	/* Create analog output instances. */
+	for (i = 0; i < me6000_versions[version_idx].ao_subdevices; i++) {
+		high_range = ((i == 8)
+			      &&
+			      ((me6000_device->base.info.pci.device_id ==
+				PCI_DEVICE_ID_MEILHAUS_ME6359)
+			       || (me6000_device->base.info.pci.device_id ==
+				   PCI_DEVICE_ID_MEILHAUS_ME6259)
+			      )
+		    )? 1 : 0;
+
+		fifo =
+		    (i <
+		     me6000_versions[version_idx].
+		     ao_fifo) ? ME6000_AO_HAS_FIFO : 0x0;
+		fifo |= (i < 4) ? ME6000_AO_EXTRA_HARDWARE : 0x0;
+
+		subdevice =
+		    (me_subdevice_t *) me6000_ao_constructor(me6000_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     &me6000_device->
+							     preload_reg_lock,
+							     &me6000_device->
+							     preload_flags,
+							     &me6000_device->
+							     triggering_flags,
+							     i, fifo,
+							     me6000_device->
+							     base.irq,
+							     high_range,
+							     me6000_workqueue);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me6000_device);
+			kfree(me6000_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me6000_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me6000_device;
+}
+
+// Init and exit of module.
+
+static int __init me6000_init(void)
+{
+	PDEBUG("executed.\n");
+
+	me6000_workqueue = create_singlethread_workqueue("me6000");
+	return 0;
+}
+
+static void __exit me6000_exit(void)
+{
+	PDEBUG("executed.\n");
+
+	flush_workqueue(me6000_workqueue);
+	destroy_workqueue(me6000_workqueue);
+}
+
+module_init(me6000_init);
+module_exit(me6000_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR
+    ("Guenter Gebhardt <g.gebhardt@meilhaus.de> & Krzysztof Gantzke <k.gantzke@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for ME-6000 Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus ME-6000 Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me6000_pci_constructor);

+ 149 - 0
drivers/staging/meilhaus/me6000_device.h

@@ -0,0 +1,149 @@
+/**
+ * @file me6000_device.h
+ *
+ * @brief ME-6000 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_DEVICE_H
+#define _ME6000_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-6000 device capabilities.
+ */
+typedef struct me6000_version {
+	uint16_t device_id;
+	unsigned int dio_subdevices;
+	unsigned int ao_subdevices;
+	unsigned int ao_fifo;	//How many devices have FIFO
+} me6000_version_t;
+
+/**
+ * @brief ME-6000 device capabilities.
+ */
+static me6000_version_t me6000_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME6004, 0, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6008, 0, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME600F, 0, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6014, 0, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6018, 0, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME601F, 0, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6034, 0, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6038, 0, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME603F, 0, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6104, 0, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6108, 0, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME610F, 0, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6114, 0, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6118, 0, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME611F, 0, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6134, 0, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6138, 0, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME613F, 0, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6044, 2, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6048, 2, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME604F, 2, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6054, 2, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6058, 2, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME605F, 2, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6074, 2, 4, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME6078, 2, 8, 0},
+	{PCI_DEVICE_ID_MEILHAUS_ME607F, 2, 16, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6144, 2, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6148, 2, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME614F, 2, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6154, 2, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6158, 2, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME615F, 2, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6174, 2, 4, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME6178, 2, 8, 4},
+	{PCI_DEVICE_ID_MEILHAUS_ME617F, 2, 16, 4},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6259, 2, 9, 0},
+
+	{PCI_DEVICE_ID_MEILHAUS_ME6359, 2, 9, 4},
+
+	{0},
+};
+
+#define ME6000_DEVICE_VERSIONS (sizeof(me6000_versions) / sizeof(me6000_version_t) - 1)	/**< Returns the number of entries in #me6000_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me6000_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me6000_versions.
+ */
+static inline unsigned int me6000_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME6000_DEVICE_VERSIONS; i++)
+		if (me6000_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-6000 device class structure.
+ */
+typedef struct me6000_device {
+	me_device_t base;			/**< The Meilhaus device base class. */
+
+	/* Child class attributes. */
+	spinlock_t preload_reg_lock;		/**< Guards the preload register. */
+	uint32_t preload_flags;
+	uint32_t triggering_flags;
+
+	spinlock_t dio_ctrl_reg_lock;
+} me6000_device_t;
+
+/**
+ * @brief The ME-6000 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-6000 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me6000_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 415 - 0
drivers/staging/meilhaus/me6000_dio.c

@@ -0,0 +1,415 @@
+/**
+ * @file me6000_dio.c
+ *
+ * @brief ME-6000 digital input/output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me6000_dio_reg.h"
+#include "me6000_dio.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me6000_dio_io_reset_subdevice(struct me_subdevice *subdevice,
+					 struct file *filep, int flags)
+{
+	me6000_dio_subdevice_t *instance;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me6000_dio_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	mode &= ~(0x3 << (instance->dio_idx * 2));
+	outb(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outb(0x00, instance->port_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, 0x00);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_dio_io_single_config(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int single_config,
+				       int ref,
+				       int trig_chan,
+				       int trig_type, int trig_edge, int flags)
+{
+	me6000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+	int size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+
+	PDEBUG("executed.\n");
+
+	instance = (me6000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+				mode &=
+				    ~((ME6000_DIO_CTRL_BIT_MODE_0 |
+				       ME6000_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				mode &=
+				    ~((ME6000_DIO_CTRL_BIT_MODE_0 |
+				       ME6000_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+				mode |=
+				    ME6000_DIO_CTRL_BIT_MODE_0 << (instance->
+								   dio_idx * 2);
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outb(mode, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, mode);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_dio_io_single_read(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int *value, int time_out, int flags)
+{
+	me6000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me6000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME6000_DIO_CTRL_BIT_MODE_0 |
+					      ME6000_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+			if ((mode ==
+			     (ME6000_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value =
+				    inb(instance->port_reg) & (0x1 << channel);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME6000_DIO_CTRL_BIT_MODE_0 |
+					      ME6000_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+			if ((mode ==
+			     (ME6000_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value = inb(instance->port_reg) & 0x00FF;
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_dio_io_single_write(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int value, int time_out, int flags)
+{
+	me6000_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+	uint8_t byte;
+
+	PDEBUG("executed.\n");
+
+	instance = (me6000_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME6000_DIO_CTRL_BIT_MODE_0 |
+					      ME6000_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME6000_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				byte = inb(instance->port_reg) & 0x00FF;
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outb(byte, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME6000_DIO_CTRL_BIT_MODE_0 |
+					      ME6000_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME6000_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				outb(value, instance->port_reg);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me6000_dio_query_number_channels(me_subdevice_t * subdevice,
+					    int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_dio_query_subdevice_type(me_subdevice_t * subdevice,
+					   int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me6000_dio_query_subdevice_caps(me_subdevice_t * subdevice,
+					   int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_DIR_BYTE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me6000_dio_subdevice_t *me6000_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock)
+{
+	me6000_dio_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me6000_dio_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me6000_dio_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+
+	/* Set the subdevice ports */
+	subdevice->ctrl_reg = reg_base + ME6000_DIO_CTRL_REG;
+	switch (dio_idx) {
+	case 0:
+		subdevice->port_reg = reg_base + ME6000_DIO_PORT_0_REG;
+		break;
+	case 1:
+		subdevice->port_reg = reg_base + ME6000_DIO_PORT_1_REG;
+		break;
+	default:
+		err = ME_ERRNO_INVALID_SUBDEVICE;
+	}
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save digital i/o index */
+	subdevice->dio_idx = dio_idx;
+
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me6000_dio_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me6000_dio_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me6000_dio_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me6000_dio_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me6000_dio_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me6000_dio_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me6000_dio_query_subdevice_caps;
+
+	return subdevice;
+}

+ 68 - 0
drivers/staging/meilhaus/me6000_dio.h

@@ -0,0 +1,68 @@
+/**
+ * @file me6000_dio.h
+ *
+ * @brief ME-6000 digital input/output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_DIO_H_
+#define _ME6000_DIO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me6000_dio_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+	unsigned int dio_idx;			/**< The index of the digital i/o on the device. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me6000_dio_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-6000 digital input/ouput subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param dio_idx The index of the digital i/o port on the device.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me6000_dio_subdevice_t *me6000_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 43 - 0
drivers/staging/meilhaus/me6000_dio_reg.h

@@ -0,0 +1,43 @@
+/**
+ * @file me6000_dio_reg.h
+ *
+ * @brief ME-6000 digital input/output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_DIO_REG_H_
+#define _ME6000_DIO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME6000_DIO_CTRL_REG				0x00	// R/W
+#define ME6000_DIO_PORT_0_REG			0x01	// R/W
+#define ME6000_DIO_PORT_1_REG			0x02	// R/W
+#define ME6000_DIO_PORT_REG				ME6000_DIO_PORT_0_REG	// R/W
+
+#define ME6000_DIO_CTRL_BIT_MODE_0		0x01
+#define ME6000_DIO_CTRL_BIT_MODE_1		0x02
+#define ME6000_DIO_CTRL_BIT_MODE_2		0x04
+#define ME6000_DIO_CTRL_BIT_MODE_3		0x08
+
+#endif
+#endif

+ 35 - 0
drivers/staging/meilhaus/me6000_reg.h

@@ -0,0 +1,35 @@
+/**
+ * @file me6000_reg.h
+ *
+ * @brief ME-6000 device register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME6000_REG_H_
+#define _ME6000_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME6000_INIT_XILINX_REG		0xAC	// R/-
+
+#endif
+#endif

+ 187 - 0
drivers/staging/meilhaus/me8100_device.c

@@ -0,0 +1,187 @@
+/**
+ * @file me8100_device.c
+ *
+ * @brief ME-8100 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "medevice.h"
+#include "me8100_device.h"
+#include "mesubdevice.h"
+#include "me8100_di.h"
+#include "me8100_do.h"
+#include "me8254.h"
+
+me_device_t *me8100_pci_constructor(struct pci_dev *pci_device)
+{
+	me8100_device_t *me8100_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me8100_device = kmalloc(sizeof(me8100_device_t), GFP_KERNEL);
+
+	if (!me8100_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me8100_device, 0, sizeof(me8100_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me8100_device, pci_device);
+
+	if (err) {
+		kfree(me8100_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me8100_versions_get_device_index(me8100_device->base.info.pci.
+					     device_id);
+
+	// Initialize spin lock .
+	spin_lock_init(&me8100_device->dio_ctrl_reg_lock);
+	spin_lock_init(&me8100_device->ctr_ctrl_reg_lock);
+	spin_lock_init(&me8100_device->clk_src_reg_lock);
+
+	// Create subdevice instances.
+
+	for (i = 0; i < me8100_versions[version_idx].di_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8100_di_constructor(me8100_device->
+							     base.info.pci.
+							     reg_bases[2],
+							     me8100_device->
+							     base.info.pci.
+							     reg_bases[1], i,
+							     me8100_device->
+							     base.irq,
+							     &me8100_device->
+							     dio_ctrl_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8100_device);
+			kfree(me8100_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8100_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me8100_versions[version_idx].do_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8100_do_constructor(me8100_device->
+							     base.info.pci.
+							     reg_bases[2], i,
+							     &me8100_device->
+							     dio_ctrl_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8100_device);
+			kfree(me8100_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8100_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me8100_versions[version_idx].ctr_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8254_constructor(me8100_device->base.
+							  info.pci.device_id,
+							  me8100_device->base.
+							  info.pci.reg_bases[2],
+							  0, i,
+							  &me8100_device->
+							  ctr_ctrl_reg_lock,
+							  &me8100_device->
+							  clk_src_reg_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8100_device);
+			kfree(me8100_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8100_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me8100_device;
+}
+
+// Init and exit of module.
+
+static int __init me8100_init(void)
+{
+	PDEBUG("executed.\n.");
+	return ME_ERRNO_SUCCESS;
+}
+
+static void __exit me8100_exit(void)
+{
+	PDEBUG("executed.\n.");
+}
+
+module_init(me8100_init);
+
+module_exit(me8100_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR("Guenter Gebhardt <g.gebhardt@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for Template Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus Template Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me8100_pci_constructor);

+ 97 - 0
drivers/staging/meilhaus/me8100_device.h

@@ -0,0 +1,97 @@
+/**
+ * @file me8100_device.h
+ *
+ * @brief ME-8100 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_DEVICE_H
+#define _ME8100_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-8100 device capabilities.
+ */
+typedef struct me8100_version {
+	uint16_t device_id;
+	unsigned int di_subdevices;
+	unsigned int do_subdevices;
+	unsigned int ctr_subdevices;
+} me8100_version_t;
+
+/**
+ * @brief Device capabilities.
+ */
+static me8100_version_t me8100_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME8100_A, 1, 1, 3},
+	{PCI_DEVICE_ID_MEILHAUS_ME8100_B, 2, 2, 3},
+	{0},
+};
+
+#define ME8100_DEVICE_VERSIONS (sizeof(me8100_versions) / sizeof(me8100_version_t) - 1)	/**< Returns the number of entries in #me8100_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me8100_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me8100_versions.
+ */
+static inline unsigned int me8100_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME8100_DEVICE_VERSIONS; i++)
+		if (me8100_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-8100 device class structure.
+ */
+typedef struct me8100_device {
+	me_device_t base;			/**< The Meilhaus device base class. */
+
+	/* Child class attributes. */
+	spinlock_t dio_ctrl_reg_lock;
+	spinlock_t ctr_ctrl_reg_lock;
+	spinlock_t clk_src_reg_lock;
+} me8100_device_t;
+
+/**
+ * @brief The ME-8100 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-8100 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me8100_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 693 - 0
drivers/staging/meilhaus/me8100_di.c

@@ -0,0 +1,693 @@
+/**
+ * @file me8100_di.c
+ *
+ * @brief ME-8100 digital input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/version.h>
+
+#include "medefines.h"
+#include "meerror.h"
+
+#include "meids.h"
+#include "medebug.h"
+#include "meplx_reg.h"
+#include "me8100_reg.h"
+#include "me8100_di_reg.h"
+#include "me8100_di.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me8100_di_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	unsigned short ctrl;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inw(instance->ctrl_reg);
+	ctrl &= ~(ME8100_DIO_CTRL_BIT_INTB_1 | ME8100_DIO_CTRL_BIT_INTB_0);
+	outw(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outw(0, instance->mask_reg);
+	PDEBUG_REG("mask_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->mask_reg - instance->reg_base, 0);
+	outw(0, instance->pattern_reg);
+	PDEBUG_REG("pattern_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->pattern_reg - instance->reg_base, 0);
+	instance->rised = -1;
+	instance->irq_count = 0;
+	instance->filtering_flag = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	outl(PLX_INTCSR_LOCAL_INT1_EN |
+	     PLX_INTCSR_LOCAL_INT1_POL |
+	     PLX_INTCSR_LOCAL_INT2_EN |
+	     PLX_INTCSR_LOCAL_INT2_POL |
+	     PLX_INTCSR_PCI_INT_EN, instance->irq_status_reg);
+	PDEBUG_REG("plx:irq_status_reg outl(0x%lX)=0x%x\n",
+		   instance->irq_status_reg,
+		   PLX_INTCSR_LOCAL_INT1_EN | PLX_INTCSR_LOCAL_INT1_POL |
+		   PLX_INTCSR_LOCAL_INT2_EN | PLX_INTCSR_LOCAL_INT2_POL |
+		   PLX_INTCSR_PCI_INT_EN);
+
+	wake_up_interruptible_all(&instance->wait_queue);
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_di_io_irq_start(me_subdevice_t * subdevice,
+				  struct file *filep,
+				  int channel,
+				  int irq_source,
+				  int irq_edge, int irq_arg, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint16_t ctrl;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		if (flags &
+		    ~(ME_IO_IRQ_START_PATTERN_FILTERING |
+		      ME_IO_IRQ_START_DIO_WORD)) {
+			PERROR("Invalid flag specified.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if (irq_edge != ME_IRQ_EDGE_NOT_USED) {
+			PERROR("Invalid irq edge specified.\n");
+			return ME_ERRNO_INVALID_IRQ_EDGE;
+		}
+	} else if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		if (flags &
+		    ~(ME_IO_IRQ_START_EXTENDED_STATUS |
+		      ME_IO_IRQ_START_DIO_WORD)) {
+			PERROR("Invalid flag specified.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if (irq_edge != ME_IRQ_EDGE_ANY) {
+			PERROR("Invalid irq edge specified.\n");
+			return ME_ERRNO_INVALID_IRQ_EDGE;
+		}
+
+		if (!(irq_arg & 0xFFFF)) {
+			PERROR("No mask specified.\n");
+			return ME_ERRNO_INVALID_IRQ_ARG;
+		}
+	} else {
+		PERROR("Invalid irq source specified.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		outw(irq_arg, instance->pattern_reg);
+		instance->compare_value = irq_arg;
+		instance->filtering_flag =
+		    (flags & ME_IO_IRQ_START_PATTERN_FILTERING) ? 1 : 0;
+	}
+	if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		outw(irq_arg, instance->mask_reg);
+	}
+
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inw(instance->ctrl_reg);
+	ctrl |= ME8100_DIO_CTRL_BIT_INTB_0;
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		ctrl &= ~ME8100_DIO_CTRL_BIT_INTB_1;
+	}
+
+	if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		ctrl |= ME8100_DIO_CTRL_BIT_INTB_1;
+	}
+	outw(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	instance->rised = 0;
+	instance->status_value = 0;
+	instance->status_value_edges = 0;
+	instance->line_value = inw(instance->port_reg);
+	instance->status_flag = flags & ME_IO_IRQ_START_EXTENDED_STATUS;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_di_io_irq_wait(me_subdevice_t * subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *irq_count,
+				 int *value, int time_out, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	unsigned long cpu_flags;
+	int count;
+
+	PDEBUG("executed.\n");
+	PDEVELOP("PID: %d.\n", current->pid);
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	if (flags &
+	    ~(ME_IO_IRQ_WAIT_NORMAL_STATUS | ME_IO_IRQ_WAIT_EXTENDED_STATUS)) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+		count = instance->irq_count;
+
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     ((count !=
+							       instance->
+							       irq_count)
+							      || (instance->
+								  rised < 0)),
+							     t);
+//                      t = wait_event_interruptible_timeout(instance->wait_queue, (instance->rised != 0), t);
+			if (t == 0) {
+				PERROR("Wait on interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 ((count != instance->irq_count)
+						  || (instance->rised < 0)));
+//                      wait_event_interruptible(instance->wait_queue, (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	*irq_count = instance->irq_count;
+	if (!err) {
+		if (flags & ME_IO_IRQ_WAIT_NORMAL_STATUS) {
+			*value = instance->status_value;
+		} else if (flags & ME_IO_IRQ_WAIT_EXTENDED_STATUS) {
+			*value = instance->status_value_edges;
+		} else {	// Use default
+			if (!instance->status_flag) {
+				*value = instance->status_value;
+			} else {
+				*value = instance->status_value_edges;
+			}
+		}
+		instance->rised = 0;
+/*
+			instance->status_value = 0;
+			instance->status_value_edges = 0;
+*/
+	} else {
+		*value = 0;
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_di_io_irq_stop(me_subdevice_t * subdevice,
+				 struct file *filep, int channel, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	uint16_t ctrl;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inw(instance->ctrl_reg);
+	ctrl &= ~(ME8100_DIO_CTRL_BIT_INTB_1 | ME8100_DIO_CTRL_BIT_INTB_0);
+	outw(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->ctrl_reg_lock);
+	instance->rised = -1;
+	instance->status_value = 0;
+	instance->status_value_edges = 0;
+	instance->filtering_flag = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_di_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_WORD:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_di_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me8100_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+
+	switch (flags) {
+
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 16)) {
+			*value = inw(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inw(instance->port_reg) & 0xFF;
+		} else if (channel == 1) {
+			*value = (inw(instance->port_reg) >> 8) & 0xFF;
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if (channel == 0) {
+			*value = inw(instance->port_reg);
+		} else {
+			PERROR("Invalid word number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_di_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 16;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_di_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_di_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_BIT_PATTERN_IRQ | ME_CAPS_DIO_BIT_MASK_IRQ_EDGE_ANY;
+	return ME_ERRNO_SUCCESS;
+}
+
+static void me8100_di_destructor(struct me_subdevice *subdevice)
+{
+	me8100_di_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) subdevice;
+
+	free_irq(instance->irq, (void *)instance);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8100_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me8100_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me8100_di_subdevice_t *instance;
+	uint32_t icsr;
+
+	uint16_t irq_status;
+	uint16_t line_value = 0;
+
+	uint32_t status_val = 0;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_di_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	icsr = inl(instance->irq_status_reg);
+	if (instance->di_idx == 0) {
+
+		if ((icsr &
+		     (PLX_INTCSR_LOCAL_INT1_STATE | PLX_INTCSR_PCI_INT_EN |
+		      PLX_INTCSR_LOCAL_INT1_EN)) !=
+		    (PLX_INTCSR_LOCAL_INT1_STATE | PLX_INTCSR_PCI_INT_EN |
+		     PLX_INTCSR_LOCAL_INT1_EN)) {
+			PINFO
+			    ("%ld Shared interrupt. %s(): idx=0 plx:irq_status_reg=0x%04X\n",
+			     jiffies, __FUNCTION__, icsr);
+			return IRQ_NONE;
+		}
+	} else if (instance->di_idx == 1) {
+		if ((icsr &
+		     (PLX_INTCSR_LOCAL_INT2_STATE | PLX_INTCSR_PCI_INT_EN |
+		      PLX_INTCSR_LOCAL_INT2_EN)) !=
+		    (PLX_INTCSR_LOCAL_INT2_STATE | PLX_INTCSR_PCI_INT_EN |
+		     PLX_INTCSR_LOCAL_INT2_EN)) {
+			PINFO
+			    ("%ld Shared interrupt. %s(): idx=1 plx:irq_status_reg=0x%04X\n",
+			     jiffies, __FUNCTION__, icsr);
+			return IRQ_NONE;
+		}
+	} else {
+		PERROR("%s():Wrong interrupt idx=%d csr=0x%X.\n", __FUNCTION__,
+		       instance->di_idx, icsr);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("me8100_isr():Interrupt from idx=%d occured.\n",
+	       instance->di_idx);
+	spin_lock(&instance->subdevice_lock);
+	inw(instance->irq_reset_reg);
+	line_value = inw(instance->port_reg);
+
+	irq_status = instance->line_value ^ line_value;
+
+	// Make extended information.
+	status_val |= (0x00FF & (~(uint16_t) instance->line_value & line_value)) << 16;	//Raise
+	status_val |= (0x00FF & ((uint16_t) instance->line_value & ~line_value));	//Fall
+
+	instance->line_value = line_value;
+
+	if (instance->rised == 0) {
+		instance->status_value = irq_status;
+		instance->status_value_edges = status_val;
+	} else {
+		instance->status_value |= irq_status;
+		instance->status_value_edges |= status_val;
+	}
+
+	if (instance->filtering_flag) {	// For compare mode only.
+		if (instance->compare_value == instance->line_value) {
+			instance->rised = 1;
+			instance->irq_count++;
+		}
+	} else {
+		instance->rised = 1;
+		instance->irq_count++;
+	}
+
+	spin_unlock(&instance->subdevice_lock);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+me8100_di_subdevice_t *me8100_di_constructor(uint32_t me8100_reg_base,
+					     uint32_t plx_reg_base,
+					     unsigned int di_idx,
+					     int irq,
+					     spinlock_t * ctrl_reg_lock)
+{
+	me8100_di_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8100_di_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8100_di_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the subdevice index. */
+	subdevice->di_idx = di_idx;
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Register interrupt service routine. */
+	subdevice->irq = irq;
+	err = request_irq(subdevice->irq, me8100_isr,
+#ifdef IRQF_DISABLED
+			  IRQF_DISABLED | IRQF_SHARED,
+#else
+			  SA_INTERRUPT | SA_SHIRQ,
+#endif
+			  ME8100_NAME, (void *)subdevice);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", subdevice->irq);
+
+	/* Initialize the registers */
+	subdevice->ctrl_reg =
+	    me8100_reg_base + ME8100_CTRL_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->port_reg =
+	    me8100_reg_base + ME8100_DI_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->mask_reg =
+	    me8100_reg_base + ME8100_MASK_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->pattern_reg =
+	    me8100_reg_base + ME8100_PATTERN_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->din_int_reg =
+	    me8100_reg_base + ME8100_INT_DI_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->irq_reset_reg =
+	    me8100_reg_base + ME8100_RES_INT_REG_A + di_idx * ME8100_REG_OFFSET;
+	subdevice->irq_status_reg = plx_reg_base + PLX_INTCSR;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = me8100_reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_irq_start = me8100_di_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me8100_di_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me8100_di_io_irq_stop;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8100_di_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me8100_di_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8100_di_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8100_di_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8100_di_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8100_di_query_subdevice_caps;
+	subdevice->base.me_subdevice_destructor = me8100_di_destructor;
+
+	subdevice->rised = 0;
+	subdevice->irq_count = 0;
+
+	return subdevice;
+}

+ 89 - 0
drivers/staging/meilhaus/me8100_di.h

@@ -0,0 +1,89 @@
+/**
+ * @file me8100_di.h
+ *
+ * @brief ME-8100 digital input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_DI_H_
+#define _ME8100_DI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me8100_di_subdevice {
+	// Inheritance
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;
+
+	unsigned di_idx;
+
+	int irq;
+	volatile int rised;
+	unsigned int irq_count;
+
+	uint status_flag;				/**< Default interupt status flag */
+	uint status_value;				/**< Interupt status */
+	uint status_value_edges;		/**< Extended interupt status */
+	uint line_value;
+
+	uint16_t compare_value;
+	uint8_t filtering_flag;
+
+	wait_queue_head_t wait_queue;
+
+	unsigned long ctrl_reg;
+	unsigned long port_reg;
+	unsigned long mask_reg;
+	unsigned long pattern_reg;
+	unsigned long long din_int_reg;
+	unsigned long irq_reset_reg;
+	unsigned long irq_status_reg;
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+
+} me8100_di_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-8100 digital input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8100_di_subdevice_t *me8100_di_constructor(uint32_t me8100_reg_base,
+					     uint32_t plx_reg_base,
+					     unsigned int di_idx,
+					     int irq,
+					     spinlock_t * ctrl_leg_lock);
+
+#endif
+#endif

+ 47 - 0
drivers/staging/meilhaus/me8100_di_reg.h

@@ -0,0 +1,47 @@
+/**
+ * @file me8100_di_reg.h
+ *
+ * @brief ME-8100 digital input subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_DI_REG_H_
+#define _ME8100_DI_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8100_RES_INT_REG_A		0x02	//(r, )
+#define ME8100_DI_REG_A			0x04	//(r, )
+#define ME8100_PATTERN_REG_A		0x08	//( ,w)
+#define ME8100_MASK_REG_A		0x0A	//( ,w)
+#define ME8100_INT_DI_REG_A		0x0A	//(r, )
+
+#define ME8100_RES_INT_REG_B		0x0E	//(r, )
+#define ME8100_DI_REG_B			0x10	//(r, )
+#define ME8100_PATTERN_REG_B		0x14	//( ,w)
+#define ME8100_MASK_REG_B		0x16	//( ,w)
+#define ME8100_INT_DI_REG_B		0x16	//(r, )
+
+#define ME8100_REG_OFFSET		0x0C
+
+#endif
+#endif

+ 391 - 0
drivers/staging/meilhaus/me8100_do.c

@@ -0,0 +1,391 @@
+/**
+ * @file me8100_do.c
+ *
+ * @brief ME-8100 digital output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me8100_reg.h"
+#include "me8100_do_reg.h"
+#include "me8100_do.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me8100_do_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me8100_do_subdevice_t *instance;
+	uint16_t ctrl;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	ctrl = inw(instance->ctrl_reg);
+	ctrl &= ME8100_DIO_CTRL_BIT_INTB_1 | ME8100_DIO_CTRL_BIT_INTB_0;
+	outw(ctrl, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->ctrl_reg_lock);
+	outw(0, instance->port_reg);
+	instance->port_reg_mirror = 0;
+	PDEBUG_REG("port_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_do_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me8100_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	int config;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	config = inw(instance->ctrl_reg);
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_WORD:
+		if (channel == 0) {
+			if (single_config ==
+			    ME_SINGLE_CONFIG_DIO_HIGH_IMPEDANCE) {
+				config &= ~(ME8100_DIO_CTRL_BIT_ENABLE_DIO);
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_SINK) {
+				config |= ME8100_DIO_CTRL_BIT_ENABLE_DIO;
+				config &= ~ME8100_DIO_CTRL_BIT_SOURCE;
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_SOURCE) {
+				config |=
+				    ME8100_DIO_CTRL_BIT_ENABLE_DIO |
+				    ME8100_DIO_CTRL_BIT_SOURCE;
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid word number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outw(config, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outw(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, config);
+	}
+
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_do_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me8100_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 16)) {
+			*value = instance->port_reg_mirror & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = instance->port_reg_mirror & 0xFF;
+		} else if (channel == 1) {
+			*value = (instance->port_reg_mirror >> 8) & 0xFF;
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if (channel == 0) {
+			*value = instance->port_reg_mirror;
+		} else {
+			PERROR("Invalid word number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_do_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me8100_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8100_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 16)) {
+			instance->port_reg_mirror =
+			    value ? (instance->
+				     port_reg_mirror | (0x1 << channel))
+			    : (instance->port_reg_mirror & ~(0x1 << channel));
+			outw(instance->port_reg_mirror, instance->port_reg);
+			PDEBUG_REG("port_reg outw(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   instance->port_reg_mirror);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			instance->port_reg_mirror &= ~0xFF;
+			instance->port_reg_mirror |= value & 0xFF;
+			outw(instance->port_reg_mirror, instance->port_reg);
+			PDEBUG_REG("port_reg outw(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   instance->port_reg_mirror);
+		} else if (channel == 1) {
+			instance->port_reg_mirror &= ~0xFF00;
+			instance->port_reg_mirror |= (value << 8) & 0xFF00;
+			outw(instance->port_reg_mirror, instance->port_reg);
+			PDEBUG_REG("port_reg outw(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   instance->port_reg_mirror);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_WORD:
+		if (channel == 0) {
+			instance->port_reg_mirror = value;
+			outw(value, instance->port_reg);
+			PDEBUG_REG("port_reg outw(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   value);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8100_do_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 16;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_do_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_do_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_SINK_SOURCE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me8100_do_subdevice_t *me8100_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx,
+					     spinlock_t * ctrl_reg_lock)
+{
+	me8100_do_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8100_do_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8100_do_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+
+	/* Initialize registers */
+	if (do_idx == 0) {
+		subdevice->port_reg = reg_base + ME8100_DO_REG_A;
+		subdevice->ctrl_reg = reg_base + ME8100_CTRL_REG_A;
+	} else if (do_idx == 1) {
+		subdevice->port_reg = reg_base + ME8100_DO_REG_B;
+		subdevice->ctrl_reg = reg_base + ME8100_CTRL_REG_B;
+	} else {
+		PERROR("Wrong subdevice idx=%d.\n", do_idx);
+		kfree(subdevice);
+		return NULL;
+	}
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the subdevice index */
+	subdevice->do_idx = do_idx;
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8100_do_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me8100_do_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8100_do_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me8100_do_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8100_do_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8100_do_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8100_do_query_subdevice_caps;
+
+	return subdevice;
+}

+ 70 - 0
drivers/staging/meilhaus/me8100_do.h

@@ -0,0 +1,70 @@
+/**
+ * @file me8100_do.h
+ *
+ * @brief ME-8100 digital output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_DO_H_
+#define _ME8100_DO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me8100_do_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect the #ctrl_reg. */
+
+	unsigned int do_idx;
+
+	uint16_t port_reg_mirror;		/**< Mirror used to store current port register setting which is write only. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Control register. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me8100_do_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-8100 digital output subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param do_idx The index of the digital output subdevice on this device.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8100_do_subdevice_t *me8100_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx,
+					     spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 36 - 0
drivers/staging/meilhaus/me8100_do_reg.h

@@ -0,0 +1,36 @@
+/**
+ * @file me8100_ao_reg.h
+ *
+ * @brief ME-8100 analog output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_DO_REG_H_
+#define _ME8100_DO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8100_DO_REG_A			0x06	//( ,w)
+#define ME8100_DO_REG_B			0x12	//( ,w)
+
+#endif
+#endif

+ 41 - 0
drivers/staging/meilhaus/me8100_reg.h

@@ -0,0 +1,41 @@
+/**
+ * @file me8100_reg.h
+ *
+ * @brief ME-8100 register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8100_REG_H_
+#define _ME8100_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8100_CTRL_REG_A			0x00	//( ,w)
+#define ME8100_CTRL_REG_B			0x0C	//( ,w)
+
+#define ME8100_DIO_CTRL_BIT_SOURCE		0x10
+#define ME8100_DIO_CTRL_BIT_INTB_1		0x20
+#define ME8100_DIO_CTRL_BIT_INTB_0		0x40
+#define ME8100_DIO_CTRL_BIT_ENABLE_DIO		0x80
+
+#endif
+#endif

+ 194 - 0
drivers/staging/meilhaus/me8200_device.c

@@ -0,0 +1,194 @@
+/**
+ * @file me8200_device.c
+ *
+ * @brief ME-8200 device class implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+#ifndef MODULE
+#  define MODULE
+#endif
+
+#include <linux/module.h>
+
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include "meids.h"
+#include "meerror.h"
+#include "mecommon.h"
+#include "meinternal.h"
+
+#include "medebug.h"
+#include "meplx_reg.h"
+#include "medevice.h"
+#include "me8200_device.h"
+#include "mesubdevice.h"
+#include "me8200_di.h"
+#include "me8200_do.h"
+#include "me8200_dio.h"
+
+me_device_t *me8200_pci_constructor(struct pci_dev *pci_device)
+{
+	me8200_device_t *me8200_device;
+	me_subdevice_t *subdevice;
+	unsigned int version_idx;
+	int err;
+	int i;
+
+	PDEBUG("executed.\n");
+
+	// Allocate structure for device instance.
+	me8200_device = kmalloc(sizeof(me8200_device_t), GFP_KERNEL);
+
+	if (!me8200_device) {
+		PERROR("Cannot get memory for device instance.\n");
+		return NULL;
+	}
+
+	memset(me8200_device, 0, sizeof(me8200_device_t));
+
+	// Initialize base class structure.
+	err = me_device_pci_init((me_device_t *) me8200_device, pci_device);
+
+	if (err) {
+		kfree(me8200_device);
+		PERROR("Cannot initialize device base class.\n");
+		return NULL;
+	}
+
+	/* Get the index in the device version information table. */
+	version_idx =
+	    me8200_versions_get_device_index(me8200_device->base.info.pci.
+					     device_id);
+
+	// Initialize spin lock .
+	spin_lock_init(&me8200_device->irq_ctrl_lock);
+	spin_lock_init(&me8200_device->irq_mode_lock);
+	spin_lock_init(&me8200_device->dio_ctrl_lock);
+
+	/* Setup the PLX interrupt configuration */
+	outl(PLX_INTCSR_LOCAL_INT1_EN |
+	     PLX_INTCSR_LOCAL_INT1_POL |
+	     PLX_INTCSR_LOCAL_INT2_EN |
+	     PLX_INTCSR_LOCAL_INT2_POL |
+	     PLX_INTCSR_PCI_INT_EN,
+	     me8200_device->base.info.pci.reg_bases[1] + PLX_INTCSR);
+
+	// Create subdevice instances.
+
+	for (i = 0; i < me8200_versions[version_idx].di_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8200_di_constructor(me8200_device->
+							     base.info.pci.
+							     reg_bases[2], i,
+							     me8200_device->
+							     base.irq,
+							     &me8200_device->
+							     irq_ctrl_lock,
+							     &me8200_device->
+							     irq_mode_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8200_device);
+			kfree(me8200_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8200_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me8200_versions[version_idx].do_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8200_do_constructor(me8200_device->
+							     base.info.pci.
+							     reg_bases[2], i,
+							     me8200_device->
+							     base.irq,
+							     &me8200_device->
+							     irq_mode_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8200_device);
+			kfree(me8200_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8200_device->base.slist,
+					    subdevice);
+	}
+
+	for (i = 0; i < me8200_versions[version_idx].dio_subdevices; i++) {
+		subdevice =
+		    (me_subdevice_t *) me8200_dio_constructor(me8200_device->
+							      base.info.pci.
+							      reg_bases[2], i,
+							      &me8200_device->
+							      dio_ctrl_lock);
+
+		if (!subdevice) {
+			me_device_deinit((me_device_t *) me8200_device);
+			kfree(me8200_device);
+			PERROR("Cannot get memory for subdevice.\n");
+			return NULL;
+		}
+
+		me_slist_add_subdevice_tail(&me8200_device->base.slist,
+					    subdevice);
+	}
+
+	return (me_device_t *) me8200_device;
+}
+
+// Init and exit of module.
+
+static int __init me8200_init(void)
+{
+	PDEBUG("executed.\n.");
+	return 0;
+}
+
+static void __exit me8200_exit(void)
+{
+	PDEBUG("executed.\n.");
+}
+
+module_init(me8200_init);
+
+module_exit(me8200_exit);
+
+// Administrative stuff for modinfo.
+MODULE_AUTHOR("Guenter Gebhardt <g.gebhardt@meilhaus.de>");
+MODULE_DESCRIPTION("Device Driver Module for Template Device");
+MODULE_SUPPORTED_DEVICE("Meilhaus Template Devices");
+MODULE_LICENSE("GPL");
+
+// Export the constructor.
+EXPORT_SYMBOL(me8200_pci_constructor);

+ 97 - 0
drivers/staging/meilhaus/me8200_device.h

@@ -0,0 +1,97 @@
+/**
+ * @file me8200_device.h
+ *
+ * @brief ME-8200 device class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DEVICE_H
+#define _ME8200_DEVICE_H
+
+#include <linux/pci.h>
+#include <linux/spinlock.h>
+
+#include "medevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief Structure holding ME-8200 device capabilities.
+ */
+typedef struct me8200_version {
+	uint16_t device_id;
+	unsigned int di_subdevices;
+	unsigned int do_subdevices;
+	unsigned int dio_subdevices;
+} me8200_version_t;
+
+/**
+ * @brief Device capabilities.
+ */
+static me8200_version_t me8200_versions[] = {
+	{PCI_DEVICE_ID_MEILHAUS_ME8200_A, 1, 1, 2},
+	{PCI_DEVICE_ID_MEILHAUS_ME8200_B, 2, 2, 2},
+	{0},
+};
+
+#define ME8200_DEVICE_VERSIONS (sizeof(me8200_versions) / sizeof(me8200_version_t) - 1)	/**< Returns the number of entries in #me8200_versions. */
+
+/**
+ * @brief Returns the index of the device entry in #me8200_versions.
+ *
+ * @param device_id The PCI device id of the device to query.
+ * @return The index of the device in #me8200_versions.
+ */
+static inline unsigned int me8200_versions_get_device_index(uint16_t device_id)
+{
+	unsigned int i;
+	for (i = 0; i < ME8200_DEVICE_VERSIONS; i++)
+		if (me8200_versions[i].device_id == device_id)
+			break;
+	return i;
+}
+
+/**
+ * @brief The ME-8200 device class structure.
+ */
+typedef struct me8200_device {
+	me_device_t base;		/**< The Meilhaus device base class. */
+
+	/* Child class attributes. */
+	spinlock_t irq_ctrl_lock;	/**< Lock for the interrupt control register. */
+	spinlock_t irq_mode_lock;	/**< Lock for the interrupt mode register. */
+	spinlock_t dio_ctrl_lock;	/**< Lock for the digital i/o control register. */
+} me8200_device_t;
+
+/**
+ * @brief The ME-8200 device class constructor.
+ *
+ * @param pci_device The pci device structure given by the PCI subsystem.
+ *
+ * @return On succes a new ME-8200 device instance. \n
+ *         NULL on error.
+ */
+me_device_t *me8200_pci_constructor(struct pci_dev *pci_device)
+    __attribute__ ((weak));
+
+#endif
+#endif

+ 857 - 0
drivers/staging/meilhaus/me8200_di.c

@@ -0,0 +1,857 @@
+/**
+ * @file me8200_di.c
+ *
+ * @brief ME-8200 digital input subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+///Includes
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/version.h>
+
+#include "medefines.h"
+#include "meerror.h"
+
+#include "meids.h"
+#include "medebug.h"
+#include "me8200_reg.h"
+#include "me8200_di_reg.h"
+#include "me8200_di.h"
+
+/// Defines
+static void me8200_di_destructor(struct me_subdevice *subdevice);
+static int me8200_di_io_irq_start(me_subdevice_t * subdevice,
+				  struct file *filep,
+				  int channel,
+				  int irq_source,
+				  int irq_edge, int irq_arg, int flags);
+static int me8200_di_io_irq_wait(me_subdevice_t * subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *irq_count,
+				 int *value, int time_out, int flags);
+static int me8200_di_io_irq_stop(me_subdevice_t * subdevice,
+				 struct file *filep, int channel, int flags);
+static int me8200_di_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags);
+static int me8200_di_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags);
+static int me8200_di_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags);
+static int me8200_di_query_number_channels(me_subdevice_t * subdevice,
+					   int *number);
+static int me8200_di_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype);
+static int me8200_di_query_subdevice_caps(me_subdevice_t * subdevice,
+					  int *caps);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8200_isr(int irq, void *dev_id);
+#else
+static irqreturn_t me8200_isr(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8200_isr_EX(int irq, void *dev_id);
+#else
+static irqreturn_t me8200_isr_EX(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+static void me8200_di_check_version(me8200_di_subdevice_t * instance,
+				    unsigned long addr);
+
+///Functions
+static int me8200_di_io_irq_start(me_subdevice_t * subdevice,
+				  struct file *filep,
+				  int channel,
+				  int irq_source,
+				  int irq_edge, int irq_arg, int flags)
+{
+	me8200_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	volatile uint8_t tmp;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		if (flags &
+		    ~(ME_IO_IRQ_START_PATTERN_FILTERING |
+		      ME_IO_IRQ_START_DIO_BYTE)) {
+			PERROR("Invalid flag specified.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if (irq_edge != ME_IRQ_EDGE_NOT_USED) {
+			PERROR("Invalid irq edge specified.\n");
+			return ME_ERRNO_INVALID_IRQ_EDGE;
+		}
+	} else if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		if (flags &
+		    ~(ME_IO_IRQ_START_EXTENDED_STATUS |
+		      ME_IO_IRQ_START_DIO_BYTE)) {
+			PERROR("Invalid flag specified.\n");
+			return ME_ERRNO_INVALID_FLAGS;
+		}
+
+		if ((irq_edge != ME_IRQ_EDGE_RISING)
+		    && (irq_edge != ME_IRQ_EDGE_FALLING)
+		    && (irq_edge != ME_IRQ_EDGE_ANY)) {
+			PERROR("Invalid irq edge specified.\n");
+			return ME_ERRNO_INVALID_IRQ_EDGE;
+		}
+
+		if (!(irq_arg & 0xFF)) {
+			PERROR("No mask specified.\n");
+			return ME_ERRNO_INVALID_IRQ_ARG;
+		}
+	} else {
+		PERROR("Invalid irq source specified.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		outb(irq_arg, instance->compare_reg);
+		PDEBUG_REG("compare_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->compare_reg - instance->reg_base, irq_arg);
+		outb(0xFF, instance->mask_reg);
+		PDEBUG_REG("mask_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->mask_reg - instance->reg_base, 0xff);
+		instance->compare_value = irq_arg;
+		instance->filtering_flag =
+		    (flags & ME_IO_IRQ_START_PATTERN_FILTERING) ? 1 : 0;
+	}
+	if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		outb(irq_arg, instance->mask_reg);
+		PDEBUG_REG("mask_reg outb(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->mask_reg - instance->reg_base, irq_arg);
+		instance->filtering_flag = 0;
+	}
+
+	spin_lock(instance->irq_mode_lock);
+	tmp = inb(instance->irq_mode_reg);
+	tmp &=
+	    ~(ME8200_IRQ_MODE_MASK <<
+	      (ME8200_IRQ_MODE_DI_SHIFT * instance->di_idx));
+	if (irq_source == ME_IRQ_SOURCE_DIO_PATTERN) {
+		tmp |=
+		    ME8200_IRQ_MODE_MASK_COMPARE << (ME8200_IRQ_MODE_DI_SHIFT *
+						     instance->di_idx);
+	}
+
+	if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		tmp |=
+		    ME8200_IRQ_MODE_MASK_MASK << (ME8200_IRQ_MODE_DI_SHIFT *
+						  instance->di_idx);
+	}
+	outb(tmp, instance->irq_mode_reg);
+	PDEBUG_REG("irq_mode_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_mode_reg - instance->reg_base, tmp);
+	spin_unlock(instance->irq_mode_lock);
+
+	spin_lock(instance->irq_ctrl_lock);
+	tmp = inb(instance->irq_ctrl_reg);
+	tmp |=
+	    (ME8200_DI_IRQ_CTRL_BIT_CLEAR <<
+	     (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	tmp |=
+	    ME8200_DI_IRQ_CTRL_BIT_ENABLE << (ME8200_DI_IRQ_CTRL_SHIFT *
+					      instance->di_idx);
+
+	if (irq_source == ME_IRQ_SOURCE_DIO_MASK) {
+		tmp &=
+		    ~(ME8200_DI_IRQ_CTRL_MASK_EDGE <<
+		      (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+		if (irq_edge == ME_IRQ_EDGE_RISING) {
+			tmp |=
+			    ME8200_DI_IRQ_CTRL_MASK_EDGE_RISING <<
+			    (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx);
+		} else if (irq_edge == ME_IRQ_EDGE_FALLING) {
+			tmp |=
+			    ME8200_DI_IRQ_CTRL_MASK_EDGE_FALLING <<
+			    (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx);
+		} else if (irq_edge == ME_IRQ_EDGE_ANY) {
+			tmp |=
+			    ME8200_DI_IRQ_CTRL_MASK_EDGE_ANY <<
+			    (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx);
+		}
+	}
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	tmp &=
+	    ~(ME8200_DI_IRQ_CTRL_BIT_CLEAR <<
+	      (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+
+	instance->line_value = inb(instance->port_reg);
+	spin_unlock(instance->irq_ctrl_lock);
+
+	instance->rised = 0;
+	instance->status_value = 0;
+	instance->status_value_edges = 0;
+	instance->status_flag = flags & ME_IO_IRQ_START_EXTENDED_STATUS;
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_di_io_irq_wait(me_subdevice_t * subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *irq_count,
+				 int *value, int time_out, int flags)
+{
+	me8200_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	unsigned long cpu_flags;
+	int count;
+
+	PDEBUG("executed.\n");
+	PDEVELOP("PID: %d.\n", current->pid);
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	if (flags &
+	    ~(ME_IO_IRQ_WAIT_NORMAL_STATUS | ME_IO_IRQ_WAIT_EXTENDED_STATUS)) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+		count = instance->count;
+
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     ((count !=
+							       instance->count)
+							      || (instance->
+								  rised < 0)),
+							     t);
+//                      t = wait_event_interruptible_timeout(instance->wait_queue, (instance->rised != 0), t);
+			if (t == 0) {
+				PERROR("Wait on interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 ((count != instance->count)
+						  || (instance->rised < 0)));
+//                      wait_event_interruptible(instance->wait_queue, (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	*irq_count = instance->count;
+	if (!err) {
+		if (flags & ME_IO_IRQ_WAIT_NORMAL_STATUS) {
+			*value = instance->status_value;
+		} else if (flags & ME_IO_IRQ_WAIT_EXTENDED_STATUS) {
+			*value = instance->status_value_edges;
+		} else {	// Use default
+			if (!instance->status_flag) {
+				*value = instance->status_value;
+			} else {
+				*value = instance->status_value_edges;
+			}
+		}
+		instance->rised = 0;
+/*
+			instance->status_value = 0;
+			instance->status_value_edges = 0;
+*/
+	} else {
+		*value = 0;
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_di_io_irq_stop(me_subdevice_t * subdevice,
+				 struct file *filep, int channel, int flags)
+{
+	me8200_di_subdevice_t *instance;
+	uint8_t tmp;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER spin_lock_irqsave(&instance->subdevice_lock, status);
+	spin_lock(instance->irq_ctrl_lock);
+	tmp = inb(instance->irq_ctrl_reg);
+	tmp |=
+	    (ME8200_DI_IRQ_CTRL_BIT_ENABLE <<
+	     (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	tmp &=
+	    ~(ME8200_DI_IRQ_CTRL_BIT_ENABLE <<
+	      (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	tmp |=
+	    (ME8200_DI_IRQ_CTRL_BIT_CLEAR <<
+	     (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+//                      tmp &= ~(ME8200_DI_IRQ_CTRL_BIT_CLEAR << (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->irq_ctrl_lock);
+
+	instance->rised = -1;
+	instance->status_value = 0;
+	instance->status_value_edges = 0;
+	instance->filtering_flag = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_di_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me8200_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+			} else {
+				PERROR("Invalid port direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_di_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me8200_di_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_reg);
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_di_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me8200_di_subdevice_t *instance = (me8200_di_subdevice_t *) subdevice;
+
+	PDEBUG("executed.\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	instance->count = 0;
+	return me8200_di_io_irq_stop(subdevice, filep, 0, 0);
+}
+
+static int me8200_di_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_di_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DI;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_di_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps =
+	    ME_CAPS_DIO_BIT_PATTERN_IRQ |
+	    ME_CAPS_DIO_BIT_MASK_IRQ_EDGE_RISING |
+	    ME_CAPS_DIO_BIT_MASK_IRQ_EDGE_FALLING |
+	    ME_CAPS_DIO_BIT_MASK_IRQ_EDGE_ANY;
+	return ME_ERRNO_SUCCESS;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8200_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me8200_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me8200_di_subdevice_t *instance;
+	uint8_t ctrl;
+	uint8_t irq_status;
+	uint8_t line_value = 0;
+	uint8_t line_status = 0;
+	uint32_t status_val = 0;
+
+	instance = (me8200_di_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inb(instance->irq_status_reg);
+	if (!irq_status) {
+		PINFO
+		    ("%ld Shared interrupt. %s(): idx=%d irq_status_reg=0x%04X\n",
+		     jiffies, __FUNCTION__, instance->di_idx, irq_status);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->irq_ctrl_lock);
+	ctrl = inb(instance->irq_ctrl_reg);
+	ctrl |=
+	    ME8200_DI_IRQ_CTRL_BIT_CLEAR << (ME8200_DI_IRQ_CTRL_SHIFT *
+					     instance->di_idx);
+	outb(ctrl, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, ctrl);
+	ctrl &=
+	    ~(ME8200_DI_IRQ_CTRL_BIT_CLEAR <<
+	      (ME8200_DI_IRQ_CTRL_SHIFT * instance->di_idx));
+	outb(ctrl, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, ctrl);
+
+	line_value = inb(instance->port_reg);
+	spin_unlock(instance->irq_ctrl_lock);
+
+	line_status = ((uint8_t) instance->line_value ^ line_value);
+
+	// Make extended information.
+	status_val |= (0x00FF & (~(uint8_t) instance->line_value & line_value)) << 16;	//Raise
+	status_val |= (0x00FF & ((uint8_t) instance->line_value & ~line_value));	//Fall
+
+	instance->line_value = (int)line_value;
+
+	if (instance->rised == 0) {
+		instance->status_value = irq_status | line_status;
+		instance->status_value_edges = status_val;
+	} else {
+		instance->status_value |= irq_status | line_status;
+		instance->status_value_edges |= status_val;
+	}
+
+	if (instance->filtering_flag) {	// For compare mode only.
+		if (instance->compare_value == instance->line_value) {
+			instance->rised = 1;
+			instance->count++;
+		}
+	} else {
+		instance->rised = 1;
+		instance->count++;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	spin_unlock(&instance->subdevice_lock);
+
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8200_isr_EX(int irq, void *dev_id)
+#else
+static irqreturn_t me8200_isr_EX(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me8200_di_subdevice_t *instance;
+	uint8_t irq_status = 0;
+	uint16_t irq_status_EX = 0;
+	uint32_t status_val = 0;
+	int i, j;
+
+	instance = (me8200_di_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	//Reset latches. Copy status to extended registers.
+	irq_status = inb(instance->irq_status_reg);
+	PDEBUG_REG("idx=%d irq_status_reg=0x%02X\n", instance->di_idx,
+		   irq_status);
+
+	if (!irq_status) {
+		PINFO
+		    ("%ld Shared interrupt. %s(): idx=%d irq_status_reg=0x%04X\n",
+		     jiffies, __FUNCTION__, instance->di_idx, irq_status);
+		return IRQ_NONE;
+	}
+
+	irq_status_EX = inb(instance->irq_status_low_reg);
+	irq_status_EX |= (inb(instance->irq_status_high_reg) << 8);
+
+	PDEVELOP("EXTENDED REG: 0x%04x\n", irq_status_EX);
+	instance->line_value = inb(instance->port_reg);
+
+	// Format extended information.
+	for (i = 0, j = 0; i < 8; i++, j += 2) {
+		status_val |= ((0x01 << j) & irq_status_EX) >> (j - i);	//Fall
+		status_val |= ((0x01 << (j + 1)) & irq_status_EX) << (15 - j + i);	//Raise
+	}
+
+	spin_lock(&instance->subdevice_lock);
+	if (instance->rised == 0) {
+		instance->status_value = irq_status;
+		instance->status_value_edges = status_val;
+	} else {
+		instance->status_value |= irq_status;
+		instance->status_value_edges |= status_val;
+	}
+
+	if (instance->filtering_flag) {	// For compare mode only.
+		if (instance->compare_value == instance->line_value) {
+			instance->rised = 1;
+			instance->count++;
+		}
+	} else {
+		instance->rised = 1;
+		instance->count++;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+static void me8200_di_destructor(struct me_subdevice *subdevice)
+{
+	me8200_di_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_di_subdevice_t *) subdevice;
+
+	free_irq(instance->irq, (void *)instance);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+me8200_di_subdevice_t *me8200_di_constructor(uint32_t me8200_regbase,
+					     unsigned int di_idx,
+					     int irq,
+					     spinlock_t * irq_ctrl_lock,
+					     spinlock_t * irq_mode_lock)
+{
+	me8200_di_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8200_di_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8200_di_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Check firmware version.
+	me8200_di_check_version(subdevice,
+				me8200_regbase + ME8200_FIRMWARE_VERSION_REG);
+
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->irq_ctrl_lock = irq_ctrl_lock;
+	subdevice->irq_mode_lock = irq_mode_lock;
+
+	/* Save the subdevice index. */
+	subdevice->di_idx = di_idx;
+
+	/* Initialize registers */
+	if (di_idx == 0) {
+		subdevice->port_reg = me8200_regbase + ME8200_DI_PORT_0_REG;
+		subdevice->mask_reg = me8200_regbase + ME8200_DI_MASK_0_REG;
+		subdevice->compare_reg =
+		    me8200_regbase + ME8200_DI_COMPARE_0_REG;
+		subdevice->irq_status_reg =
+		    me8200_regbase + ME8200_DI_CHANGE_0_REG;
+
+		subdevice->irq_status_low_reg =
+		    me8200_regbase + ME8200_DI_EXTEND_CHANGE_0_LOW_REG;
+		subdevice->irq_status_high_reg =
+		    me8200_regbase + ME8200_DI_EXTEND_CHANGE_0_HIGH_REG;
+	} else if (di_idx == 1) {
+		subdevice->port_reg = me8200_regbase + ME8200_DI_PORT_1_REG;
+		subdevice->mask_reg = me8200_regbase + ME8200_DI_MASK_1_REG;
+		subdevice->compare_reg =
+		    me8200_regbase + ME8200_DI_COMPARE_1_REG;
+		subdevice->irq_status_reg =
+		    me8200_regbase + ME8200_DI_CHANGE_1_REG;
+
+		subdevice->irq_status_low_reg =
+		    me8200_regbase + ME8200_DI_EXTEND_CHANGE_1_LOW_REG;
+		subdevice->irq_status_high_reg =
+		    me8200_regbase + ME8200_DI_EXTEND_CHANGE_1_HIGH_REG;
+	} else {
+		PERROR("Wrong subdevice idx=%d.\n", di_idx);
+		kfree(subdevice);
+		return NULL;
+	}
+	subdevice->irq_ctrl_reg = me8200_regbase + ME8200_DI_IRQ_CTRL_REG;
+	subdevice->irq_mode_reg = me8200_regbase + ME8200_IRQ_MODE_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = me8200_regbase;
+#endif
+
+	/* Initialize wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_irq_start = me8200_di_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me8200_di_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me8200_di_io_irq_stop;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8200_di_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me8200_di_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8200_di_io_single_read;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8200_di_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8200_di_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8200_di_query_subdevice_caps;
+	subdevice->base.me_subdevice_destructor = me8200_di_destructor;
+
+	subdevice->rised = 0;
+	subdevice->count = 0;
+
+	/* Register interrupt service routine. */
+	subdevice->irq = irq;
+	if (subdevice->version > 0) {	// NEW
+		err = request_irq(subdevice->irq, me8200_isr_EX,
+#ifdef IRQF_DISABLED
+				  IRQF_DISABLED | IRQF_SHARED,
+#else
+				  SA_INTERRUPT | SA_SHIRQ,
+#endif
+				  ME8200_NAME, (void *)subdevice);
+	} else {		//OLD
+		err = request_irq(subdevice->irq, me8200_isr,
+#ifdef IRQF_DISABLED
+				  IRQF_DISABLED | IRQF_SHARED,
+#else
+				  SA_INTERRUPT | SA_SHIRQ,
+#endif
+				  ME8200_NAME, (void *)subdevice);
+	}
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	PDEBUG("Registred irq=%d.\n", subdevice->irq);
+
+	return subdevice;
+}
+
+static void me8200_di_check_version(me8200_di_subdevice_t * instance,
+				    unsigned long addr)
+{
+
+	PDEBUG("executed.\n");
+	instance->version = 0x000000FF & inb(addr);
+	PDEVELOP("me8200 firmware version: %d\n", instance->version);
+
+	/// @note Fix for wrong values in this registry.
+	if ((instance->version < 0x7) || (instance->version > 0x1F))
+		instance->version = 0x0;
+}

+ 92 - 0
drivers/staging/meilhaus/me8200_di.h

@@ -0,0 +1,92 @@
+/**
+ * @file me8200_di.h
+ *
+ * @brief ME-8200 digital input subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DI_H_
+#define _ME8200_DI_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me8200_di_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;
+	spinlock_t *irq_ctrl_lock;
+	spinlock_t *irq_mode_lock;
+
+	unsigned int di_idx;
+	unsigned int version;
+
+	int irq;						/**< The number of the interrupt request. */
+	volatile int rised;				/**< Flag to indicate if an interrupt occured. */
+	uint status_flag;				/**< Default interupt status flag */
+	uint status_value;				/**< Interupt status */
+	uint status_value_edges;			/**< Extended interupt status */
+	uint line_value;
+	int count;						/**< Counts the number of interrupts occured. */
+	uint8_t compare_value;
+	uint8_t filtering_flag;
+
+	wait_queue_head_t wait_queue;	/**< To wait on interrupts. */
+
+	unsigned long port_reg;			/**< The digital input port. */
+	unsigned long compare_reg;		/**< The register to hold the value to compare with. */
+	unsigned long mask_reg;			/**< The register to hold the mask. */
+	unsigned long irq_mode_reg;		/**< The interrupt mode register. */
+	unsigned long irq_ctrl_reg;		/**< The interrupt control register. */
+	unsigned long irq_status_reg;	/**< The interrupt status register. Also interrupt reseting register (firmware version 7 and later).*/
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+	unsigned long firmware_version_reg;	/**< The interrupt reseting register. */
+
+	unsigned long irq_status_low_reg;	/**< The interrupt extended status register (low part). */
+	unsigned long irq_status_high_reg;	/**< The interrupt extended status register (high part). */
+} me8200_di_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-8200 digital input subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8200_di_subdevice_t *me8200_di_constructor(uint32_t me8200_reg_base,
+					     unsigned int di_idx,
+					     int irq,
+					     spinlock_t * irq_ctrl_lock,
+					     spinlock_t * irq_mode_lock);
+
+#endif
+#endif

+ 75 - 0
drivers/staging/meilhaus/me8200_di_reg.h

@@ -0,0 +1,75 @@
+/**
+ * @file me8200_di_reg.h
+ *
+ * @brief ME-8200 digital input subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DI_REG_H_
+#define _ME8200_DI_REG_H_
+
+#ifdef __KERNEL__
+
+// Common registry for whole family.
+#define ME8200_DI_PORT_0_REG					0x3	// R
+#define ME8200_DI_PORT_1_REG					0x4	// R
+
+#define ME8200_DI_MASK_0_REG					0x5	// R/W
+#define ME8200_DI_MASK_1_REG					0x6	// R/W
+
+#define ME8200_DI_COMPARE_0_REG					0xA	// R/W
+#define ME8200_DI_COMPARE_1_REG					0xB	// R/W
+
+#define ME8200_DI_IRQ_CTRL_REG					0xC	// R/W
+
+#ifndef ME8200_IRQ_MODE_REG
+# define ME8200_IRQ_MODE_REG					0xD	// R/W
+#endif
+
+// This registry are for all versions
+#define ME8200_DI_CHANGE_0_REG					0xE	// R
+#define ME8200_DI_CHANGE_1_REG					0xF	// R
+
+#define ME8200_DI_IRQ_CTRL_BIT_CLEAR			0x4
+#define ME8200_DI_IRQ_CTRL_BIT_ENABLE			0x8
+
+// This registry are for firmware versions 7 and later
+#define ME8200_DI_EXTEND_CHANGE_0_LOW_REG		0x10	// R
+#define ME8200_DI_EXTEND_CHANGE_0_HIGH_REG		0x11	// R
+#define ME8200_DI_EXTEND_CHANGE_1_LOW_REG		0x12	// R
+#define ME8200_DI_EXTEND_CHANGE_1_HIGH_REG		0x13	// R
+
+#ifndef ME8200_FIRMWARE_VERSION_REG
+# define ME8200_FIRMWARE_VERSION_REG			0x14	// R
+#endif
+
+// Bit definitions
+#define ME8200_DI_IRQ_CTRL_MASK_EDGE			0x3
+#define ME8200_DI_IRQ_CTRL_MASK_EDGE_RISING		0x0
+#define ME8200_DI_IRQ_CTRL_MASK_EDGE_FALLING	0x1
+#define ME8200_DI_IRQ_CTRL_MASK_EDGE_ANY		0x3
+
+// Others
+#define ME8200_DI_IRQ_CTRL_SHIFT				4
+
+#endif
+#endif

+ 418 - 0
drivers/staging/meilhaus/me8200_dio.c

@@ -0,0 +1,418 @@
+/**
+ * @file me8200_dio.c
+ *
+ * @brief ME-8200 digital input/output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me8200_dio_reg.h"
+#include "me8200_dio.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me8200_dio_io_reset_subdevice(struct me_subdevice *subdevice,
+					 struct file *filep, int flags)
+{
+	me8200_dio_subdevice_t *instance;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	instance = (me8200_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	mode &= ~(0x3 << (instance->dio_idx * 2));
+	outb(mode, instance->ctrl_reg);
+	PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->ctrl_reg - instance->reg_base, mode);
+	spin_unlock(instance->ctrl_reg_lock);
+	outb(0x00, instance->port_reg);
+	PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0x00);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_dio_io_single_config(me_subdevice_t * subdevice,
+				       struct file *filep,
+				       int channel,
+				       int single_config,
+				       int ref,
+				       int trig_chan,
+				       int trig_type, int trig_edge, int flags)
+{
+	me8200_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint32_t mode;
+	uint32_t size =
+	    flags & (ME_IO_SINGLE_CONFIG_DIO_BIT | ME_IO_SINGLE_CONFIG_DIO_BYTE
+		     | ME_IO_SINGLE_CONFIG_DIO_WORD |
+		     ME_IO_SINGLE_CONFIG_DIO_DWORD);
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	mode = inb(instance->ctrl_reg);
+	switch (size) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+				mode &=
+				    ~((ME8200_DIO_CTRL_BIT_MODE_0 |
+				       ME8200_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+			} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+				mode &=
+				    ~((ME8200_DIO_CTRL_BIT_MODE_0 |
+				       ME8200_DIO_CTRL_BIT_MODE_1) <<
+				      (instance->dio_idx * 2));
+				mode |=
+				    ME8200_DIO_CTRL_BIT_MODE_0 << (instance->
+								   dio_idx * 2);
+			} else {
+				PERROR
+				    ("Invalid port configuration specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid channel number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid flags.\n");
+
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (!err) {
+		outb(mode, instance->ctrl_reg);
+		PDEBUG_REG("ctrl_reg outl(0x%lX+0x%lX)=0x%x\n",
+			   instance->reg_base,
+			   instance->ctrl_reg - instance->reg_base, mode);
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_dio_io_single_read(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int *value, int time_out, int flags)
+{
+	me8200_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME8200_DIO_CTRL_BIT_MODE_0 |
+					      ME8200_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if ((mode ==
+			     (ME8200_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value =
+				    inb(instance->
+					port_reg) & (0x0001 << channel);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME8200_DIO_CTRL_BIT_MODE_0 |
+					      ME8200_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if ((mode ==
+			     (ME8200_DIO_CTRL_BIT_MODE_0 <<
+			      (instance->dio_idx * 2))) || !mode) {
+				*value = inb(instance->port_reg) & 0x00FF;
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_dio_io_single_write(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int value, int time_out, int flags)
+{
+	me8200_dio_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t mode;
+	uint8_t byte;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_dio_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME8200_DIO_CTRL_BIT_MODE_0 |
+					      ME8200_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME8200_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				byte = inb(instance->port_reg);
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outb(byte, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, byte);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			mode =
+			    inb(instance->
+				ctrl_reg) & ((ME8200_DIO_CTRL_BIT_MODE_0 |
+					      ME8200_DIO_CTRL_BIT_MODE_1) <<
+					     (instance->dio_idx * 2));
+
+			if (mode ==
+			    (ME8200_DIO_CTRL_BIT_MODE_0 <<
+			     (instance->dio_idx * 2))) {
+				outb(value, instance->port_reg);
+				PDEBUG_REG("port_reg outl(0x%lX+0x%lX)=0x%x\n",
+					   instance->reg_base,
+					   instance->port_reg -
+					   instance->reg_base, value);
+			} else {
+				PERROR("Port not in output or input mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(instance->ctrl_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_dio_query_number_channels(me_subdevice_t * subdevice,
+					    int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_dio_query_subdevice_type(me_subdevice_t * subdevice,
+					   int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_dio_query_subdevice_caps(me_subdevice_t * subdevice,
+					   int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_DIR_BYTE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me8200_dio_subdevice_t *me8200_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock)
+{
+	me8200_dio_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8200_dio_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8200_dio_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save digital i/o index */
+	subdevice->dio_idx = dio_idx;
+
+	/* Save the subdevice index */
+	subdevice->ctrl_reg = reg_base + ME8200_DIO_CTRL_REG;
+	subdevice->port_reg = reg_base + ME8200_DIO_PORT_REG + dio_idx;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8200_dio_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me8200_dio_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8200_dio_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me8200_dio_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8200_dio_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8200_dio_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8200_dio_query_subdevice_caps;
+
+	return subdevice;
+}

+ 68 - 0
drivers/staging/meilhaus/me8200_dio.h

@@ -0,0 +1,68 @@
+/**
+ * @file me8200_dio.h
+ *
+ * @brief ME-8200 digital input/output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DIO_H_
+#define _ME8200_DIO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me8200_dio_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg from concurrent access. */
+	unsigned int dio_idx;			/**< The index of the digital i/o on the device. */
+
+	unsigned long port_reg;			/**< Register holding the port status. */
+	unsigned long ctrl_reg;			/**< Register to configure the port direction. */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me8200_dio_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-8200 digital input/ouput subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param dio_idx The index of the digital i/o port on the device.
+ * @param ctrl_reg_lock Spin lock protecting the control register.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8200_dio_subdevice_t *me8200_dio_constructor(uint32_t reg_base,
+					       unsigned int dio_idx,
+					       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 43 - 0
drivers/staging/meilhaus/me8200_dio_reg.h

@@ -0,0 +1,43 @@
+/**
+ * @file me8200_dio_reg.h
+ *
+ * @brief ME-8200 digital input/output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DIO_REG_H_
+#define _ME8200_DIO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8200_DIO_CTRL_REG					0x7	// R/W
+#define ME8200_DIO_PORT_0_REG				0x8	// R/W
+#define ME8200_DIO_PORT_1_REG				0x9	// R/W
+#define ME8200_DIO_PORT_REG					ME8200_DIO_PORT_0_REG	// R/W
+
+#define ME8200_DIO_CTRL_BIT_MODE_0			0x01
+#define ME8200_DIO_CTRL_BIT_MODE_1			0x02
+#define ME8200_DIO_CTRL_BIT_MODE_2			0x04
+#define ME8200_DIO_CTRL_BIT_MODE_3			0x08
+
+#endif
+#endif

+ 600 - 0
drivers/staging/meilhaus/me8200_do.c

@@ -0,0 +1,600 @@
+/**
+ * @file me8200_do.c
+ *
+ * @brief ME-8200 digital output subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke	(k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/version.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "meids.h"
+#include "medebug.h"
+#include "me8200_reg.h"
+#include "me8200_do_reg.h"
+#include "me8200_do.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static int me8200_do_io_irq_start(me_subdevice_t * subdevice,
+				  struct file *filep,
+				  int channel,
+				  int irq_source,
+				  int irq_edge, int irq_arg, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t tmp;
+	unsigned long status;
+
+	if (flags & ~ME_IO_IRQ_START_DIO_BYTE) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel != 0) {
+		PERROR("Invalid channel specified.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	if (irq_source != ME_IRQ_SOURCE_DIO_OVER_TEMP) {
+		PERROR("Invalid interrupt source specified.\n");
+		return ME_ERRNO_INVALID_IRQ_SOURCE;
+	}
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+	spin_lock(instance->irq_mode_lock);
+	tmp = inb(instance->irq_ctrl_reg);
+	tmp |=
+	    ME8200_IRQ_MODE_BIT_ENABLE_POWER << (ME8200_IRQ_MODE_POWER_SHIFT *
+						 instance->do_idx);
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->irq_mode_lock);
+	instance->rised = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_do_io_irq_wait(me_subdevice_t * subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *irq_count,
+				 int *value, int time_out, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	long t = 0;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (time_out < 0) {
+		PERROR("Invalid time_out specified.\n");
+		return ME_ERRNO_INVALID_TIMEOUT;
+	}
+
+	if (time_out) {
+		t = (time_out * HZ) / 1000;
+
+		if (t == 0)
+			t = 1;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	if (instance->rised <= 0) {
+		instance->rised = 0;
+
+		if (time_out) {
+			t = wait_event_interruptible_timeout(instance->
+							     wait_queue,
+							     (instance->rised !=
+							      0), t);
+
+			if (t == 0) {
+				PERROR
+				    ("Wait on external interrupt timed out.\n");
+				err = ME_ERRNO_TIMEOUT;
+			}
+		} else {
+			wait_event_interruptible(instance->wait_queue,
+						 (instance->rised != 0));
+		}
+
+		if (instance->rised < 0) {
+			PERROR("Wait on interrupt aborted by user.\n");
+			err = ME_ERRNO_CANCELLED;
+		}
+	}
+
+	if (signal_pending(current)) {
+		PERROR("Wait on external interrupt aborted by signal.\n");
+		err = ME_ERRNO_SIGNAL;
+	}
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	instance->rised = 0;
+	*irq_count = instance->count;
+	*value = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_do_io_irq_stop(me_subdevice_t * subdevice,
+				 struct file *filep, int channel, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	uint8_t tmp;
+	unsigned long cpu_flags;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	spin_lock(instance->irq_mode_lock);
+	tmp = inb(instance->irq_ctrl_reg);
+	tmp &=
+	    ~(ME8200_IRQ_MODE_BIT_ENABLE_POWER <<
+	      (ME8200_IRQ_MODE_POWER_SHIFT * instance->do_idx));
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->irq_mode_lock);
+	instance->rised = -1;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_do_io_reset_subdevice(struct me_subdevice *subdevice,
+					struct file *filep, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	unsigned long cpu_flags;
+	uint8_t tmp;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, cpu_flags);
+	outb(0x00, instance->port_reg);
+	PDEBUG_REG("port_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->port_reg - instance->reg_base, 0x00);
+	spin_lock(instance->irq_mode_lock);
+	tmp = inb(instance->irq_ctrl_reg);
+	tmp &=
+	    ~(ME8200_IRQ_MODE_BIT_ENABLE_POWER <<
+	      (ME8200_IRQ_MODE_POWER_SHIFT * instance->do_idx));
+	outb(tmp, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outb(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, tmp);
+	spin_unlock(instance->irq_mode_lock);
+	instance->rised = -1;
+	instance->count = 0;
+	spin_unlock_irqrestore(&instance->subdevice_lock, cpu_flags);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_do_io_single_config(me_subdevice_t * subdevice,
+				      struct file *filep,
+				      int channel,
+				      int single_config,
+				      int ref,
+				      int trig_chan,
+				      int trig_type, int trig_edge, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+	switch (flags) {
+	case ME_IO_SINGLE_CONFIG_NO_FLAGS:
+	case ME_IO_SINGLE_CONFIG_DIO_BYTE:
+		if (channel == 0) {
+			if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+			} else {
+				PERROR("Invalid byte direction specified.\n");
+				err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_do_io_single_read(me_subdevice_t * subdevice,
+				    struct file *filep,
+				    int channel,
+				    int *value, int time_out, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_do_io_single_write(me_subdevice_t * subdevice,
+				     struct file *filep,
+				     int channel,
+				     int value, int time_out, int flags)
+{
+	me8200_do_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+	uint8_t state;
+	unsigned long status;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock_irqsave(&instance->subdevice_lock, status);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			state = inb(instance->port_reg);
+			state =
+			    value ? (state | (0x1 << channel)) : (state &
+								  ~(0x1 <<
+								    channel));
+			outb(state, instance->port_reg);
+			PDEBUG_REG("port_reg outb(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   state);
+		} else {
+			PERROR("Invalid bit number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			outb(value, instance->port_reg);
+			PDEBUG_REG("port_reg outb(0x%lX+0x%lX)=0x%x\n",
+				   instance->reg_base,
+				   instance->port_reg - instance->reg_base,
+				   value);
+		} else {
+			PERROR("Invalid byte number specified.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock_irqrestore(&instance->subdevice_lock, status);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8200_do_query_number_channels(me_subdevice_t * subdevice,
+					   int *number)
+{
+	PDEBUG("executed.\n");
+	*number = 8;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_do_query_subdevice_type(me_subdevice_t * subdevice,
+					  int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8200_do_query_subdevice_caps(me_subdevice_t * subdevice, int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_OVER_TEMP_IRQ;
+	return ME_ERRNO_SUCCESS;
+}
+
+static void me8200_do_destructor(struct me_subdevice *subdevice)
+{
+	me8200_do_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8200_do_subdevice_t *) subdevice;
+
+	free_irq(instance->irq, (void *)instance);
+	me_subdevice_deinit(&instance->base);
+	kfree(instance);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static irqreturn_t me8200_do_isr(int irq, void *dev_id)
+#else
+static irqreturn_t me8200_do_isr(int irq, void *dev_id, struct pt_regs *regs)
+#endif
+{
+	me8200_do_subdevice_t *instance;
+	uint16_t ctrl;
+	uint8_t irq_status;
+
+	instance = (me8200_do_subdevice_t *) dev_id;
+
+	if (irq != instance->irq) {
+		PERROR("Incorrect interrupt num: %d.\n", irq);
+		return IRQ_NONE;
+	}
+
+	irq_status = inb(instance->irq_status_reg);
+	if (!
+	    (irq_status &
+	     (ME8200_DO_IRQ_STATUS_BIT_ACTIVE << instance->do_idx))) {
+		PINFO
+		    ("%ld Shared interrupt. %s(): idx=%d irq_status_reg=0x%04X\n",
+		     jiffies, __FUNCTION__, instance->do_idx, irq_status);
+		return IRQ_NONE;
+	}
+
+	PDEBUG("executed.\n");
+
+	spin_lock(&instance->subdevice_lock);
+	instance->rised = 1;
+	instance->count++;
+
+	spin_lock(instance->irq_mode_lock);
+	ctrl = inw(instance->irq_ctrl_reg);
+	ctrl |= ME8200_IRQ_MODE_BIT_CLEAR_POWER << instance->do_idx;
+	outw(ctrl, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, ctrl);
+	ctrl &= ~(ME8200_IRQ_MODE_BIT_CLEAR_POWER << instance->do_idx);
+	outw(ctrl, instance->irq_ctrl_reg);
+	PDEBUG_REG("irq_ctrl_reg outw(0x%lX+0x%lX)=0x%x\n", instance->reg_base,
+		   instance->irq_ctrl_reg - instance->reg_base, ctrl);
+	spin_unlock(instance->irq_mode_lock);
+	spin_unlock(&instance->subdevice_lock);
+	wake_up_interruptible_all(&instance->wait_queue);
+
+	return IRQ_HANDLED;
+}
+
+me8200_do_subdevice_t *me8200_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx,
+					     int irq,
+					     spinlock_t * irq_mode_lock)
+{
+	me8200_do_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8200_do_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for subdevice instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8200_do_subdevice_t));
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->irq_mode_lock = irq_mode_lock;
+
+	/* Save the index of the digital output */
+	subdevice->do_idx = do_idx;
+	subdevice->irq = irq;
+
+	/* Initialize the registers */
+	if (do_idx == 0) {
+		subdevice->port_reg = reg_base + ME8200_DO_PORT_0_REG;
+	} else if (do_idx == 1) {
+		subdevice->port_reg = reg_base + ME8200_DO_PORT_1_REG;
+	} else {
+		PERROR("Wrong subdevice idx=%d.\n", do_idx);
+		kfree(subdevice);
+		return NULL;
+	}
+	subdevice->irq_ctrl_reg = reg_base + ME8200_IRQ_MODE_REG;
+	subdevice->irq_status_reg = reg_base + ME8200_DO_IRQ_STATUS_REG;
+#ifdef MEDEBUG_DEBUG_REG
+	subdevice->reg_base = reg_base;
+#endif
+
+	/* Initialize the wait queue */
+	init_waitqueue_head(&subdevice->wait_queue);
+
+	/* Request the interrupt line */
+	err = request_irq(irq, me8200_do_isr,
+#ifdef IRQF_DISABLED
+			  IRQF_DISABLED | IRQF_SHARED,
+#else
+			  SA_INTERRUPT | SA_SHIRQ,
+#endif
+			  ME8200_NAME, (void *)subdevice);
+
+	if (err) {
+		PERROR("Cannot get interrupt line.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	PINFO("Registered irq=%d.\n", irq);
+
+	/* Overload base class methods. */
+	subdevice->base.me_subdevice_io_irq_start = me8200_do_io_irq_start;
+	subdevice->base.me_subdevice_io_irq_wait = me8200_do_io_irq_wait;
+	subdevice->base.me_subdevice_io_irq_stop = me8200_do_io_irq_stop;
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8200_do_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config =
+	    me8200_do_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8200_do_io_single_read;
+	subdevice->base.me_subdevice_io_single_write =
+	    me8200_do_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8200_do_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8200_do_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8200_do_query_subdevice_caps;
+	subdevice->base.me_subdevice_destructor = me8200_do_destructor;
+
+	subdevice->rised = 0;
+	subdevice->count = 0;
+
+	return subdevice;
+}

+ 75 - 0
drivers/staging/meilhaus/me8200_do.h

@@ -0,0 +1,75 @@
+/**
+ * @file me8200_do.h
+ *
+ * @brief ME-8200 digital output subdevice class.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DO_H_
+#define _ME8200_DO_H_
+
+#include "mesubdevice.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The template subdevice class.
+ */
+typedef struct me8200_do_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+	spinlock_t *irq_mode_lock;
+
+	int irq;				/**< The number of the interrupt request */
+	int rised;				/**< Flag to indicate if an interrupt occured */
+	int count;				/**< Counts the number of interrupts occured */
+	wait_queue_head_t wait_queue;		/**< To wait on interrupts */
+
+	unsigned int do_idx;			/**< The number of the digital output */
+
+	unsigned long port_reg;			/**< The digital output port */
+	unsigned long irq_ctrl_reg;		/**< The interrupt control register */
+	unsigned long irq_status_reg;		/**< The interrupt status register */
+#ifdef MEDEBUG_DEBUG_REG
+	unsigned long reg_base;
+#endif
+} me8200_do_subdevice_t;
+
+/**
+ * @brief The constructor to generate a ME-8200 digital output subdevice instance.
+ *
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param do_idx The index of the digital output subdevice on this device.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8200_do_subdevice_t *me8200_do_constructor(uint32_t reg_base,
+					     unsigned int do_idx,
+					     int irq,
+					     spinlock_t * irq_mode_lock);
+
+#endif
+#endif

+ 40 - 0
drivers/staging/meilhaus/me8200_do_reg.h

@@ -0,0 +1,40 @@
+/**
+ * @file me8200_ao_reg.h
+ *
+ * @brief ME-8200 analog output subdevice register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_DO_REG_H_
+#define _ME8200_DO_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8200_DO_IRQ_STATUS_REG			0x0	// R
+#define ME8200_DO_PORT_0_REG				0x1	// R/W
+#define ME8200_DO_PORT_1_REG				0x2	// R/W
+
+#define ME8200_DO_IRQ_STATUS_BIT_ACTIVE		0x1
+#define ME8200_DO_IRQ_STATUS_SHIFT			1
+
+#endif
+#endif

+ 46 - 0
drivers/staging/meilhaus/me8200_reg.h

@@ -0,0 +1,46 @@
+/**
+ * @file me8200_reg.h
+ *
+ * @brief ME-8200 register definitions.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8200_REG_H_
+#define _ME8200_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8200_IRQ_MODE_REG				0xD	// R/W
+
+#define ME8200_IRQ_MODE_MASK     			0x3
+
+#define ME8200_IRQ_MODE_MASK_MASK			0x0
+#define ME8200_IRQ_MODE_MASK_COMPARE			0x1
+
+#define ME8200_IRQ_MODE_BIT_ENABLE_POWER		0x10
+#define ME8200_IRQ_MODE_BIT_CLEAR_POWER			0x40
+
+#define ME8200_IRQ_MODE_DI_SHIFT			2
+#define ME8200_IRQ_MODE_POWER_SHIFT			1
+
+#endif
+#endif

+ 1176 - 0
drivers/staging/meilhaus/me8254.c

@@ -0,0 +1,1176 @@
+/**
+ * @file me8254.c
+ *
+ * @brief 8254 subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+
+#include "medebug.h"
+#include "me8254_reg.h"
+#include "me8254.h"
+
+/*
+ * Defines
+ */
+#define ME8254_NUMBER_CHANNELS 1	/**< One channel per counter. */
+#define ME8254_CTR_WIDTH 16			/**< One counter has 16 bits. */
+
+/*
+ * Functions
+ */
+
+static int me8254_io_reset_subdevice(struct me_subdevice *subdevice,
+				     struct file *filep, int flags)
+{
+	me8254_subdevice_t *instance;
+	uint8_t clk_src;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8254_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	if (instance->ctr_idx == 0)
+		outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+		     ME8254_CTRL_BIN, instance->ctrl_reg);
+	else if (instance->ctr_idx == 1)
+		outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+		     ME8254_CTRL_BIN, instance->ctrl_reg);
+	else
+		outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+		     ME8254_CTRL_BIN, instance->ctrl_reg);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outb(0x00, instance->val_reg);
+	outb(0x00, instance->val_reg);
+
+	spin_lock(instance->clk_src_reg_lock);
+	clk_src = inb(instance->clk_src_reg);
+
+	switch (instance->device_id) {
+	case PCI_DEVICE_ID_MEILHAUS_ME1400:
+	case PCI_DEVICE_ID_MEILHAUS_ME140A:
+	case PCI_DEVICE_ID_MEILHAUS_ME140B:
+	case PCI_DEVICE_ID_MEILHAUS_ME14E0:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EA:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EB:
+		if (instance->me8254_idx == 0) {
+			if (instance->ctr_idx == 0)
+				clk_src &=
+				    ~(ME1400AB_8254_A_0_CLK_SRC_10MHZ |
+				      ME1400AB_8254_A_0_CLK_SRC_QUARZ);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400AB_8254_A_1_CLK_SRC_PREV);
+			else
+				clk_src &= ~(ME1400AB_8254_A_2_CLK_SRC_PREV);
+		} else {
+			if (instance->ctr_idx == 0)
+				clk_src &=
+				    ~(ME1400AB_8254_B_0_CLK_SRC_10MHZ |
+				      ME1400AB_8254_B_0_CLK_SRC_QUARZ);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400AB_8254_B_1_CLK_SRC_PREV);
+			else
+				clk_src &= ~(ME1400AB_8254_B_2_CLK_SRC_PREV);
+		}
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		switch (instance->me8254_idx) {
+		case 0:
+		case 2:
+		case 4:
+		case 6:
+		case 8:
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400CD_8254_ACE_0_CLK_SRC_MASK);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400CD_8254_ACE_1_CLK_SRC_MASK);
+			else
+				clk_src &= ~(ME1400CD_8254_ACE_2_CLK_SRC_MASK);
+			break;
+
+		default:
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400CD_8254_BD_0_CLK_SRC_MASK);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400CD_8254_BD_1_CLK_SRC_MASK);
+			else
+				clk_src &= ~(ME1400CD_8254_BD_2_CLK_SRC_MASK);
+			break;
+		}
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME4610:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_A:
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_B:
+
+		/* No clock source register available */
+		break;
+
+	default:
+		PERROR("Invalid device type.\n");
+		err = ME_ERRNO_INTERNAL;
+	}
+
+	if (!err)
+		outb(clk_src, instance->clk_src_reg);
+
+	spin_unlock(instance->clk_src_reg_lock);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me1400_ab_ref_config(me8254_subdevice_t * instance, int ref)
+{
+	uint8_t clk_src;
+
+	spin_lock(instance->clk_src_reg_lock);
+	clk_src = inb(instance->clk_src_reg);
+
+	switch (ref) {
+	case ME_REF_CTR_EXTERNAL:
+		if (instance->me8254_idx == 0) {
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400AB_8254_A_0_CLK_SRC_QUARZ);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400AB_8254_A_1_CLK_SRC_PREV);
+			else
+				clk_src &= ~(ME1400AB_8254_A_2_CLK_SRC_PREV);
+		} else {
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400AB_8254_B_0_CLK_SRC_QUARZ);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400AB_8254_B_1_CLK_SRC_PREV);
+			else
+				clk_src &= ~(ME1400AB_8254_B_2_CLK_SRC_PREV);
+		}
+
+		break;
+
+	case ME_REF_CTR_PREVIOUS:
+		if (instance->me8254_idx == 0) {
+			if (instance->ctr_idx == 0) {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			} else if (instance->ctr_idx == 1)
+				clk_src |= (ME1400AB_8254_A_1_CLK_SRC_PREV);
+			else
+				clk_src |= (ME1400AB_8254_A_2_CLK_SRC_PREV);
+		} else {
+			if (instance->ctr_idx == 0) {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			} else if (instance->ctr_idx == 1)
+				clk_src |= (ME1400AB_8254_B_1_CLK_SRC_PREV);
+			else
+				clk_src |= (ME1400AB_8254_B_2_CLK_SRC_PREV);
+		}
+
+		break;
+
+	case ME_REF_CTR_INTERNAL_1MHZ:
+		if (instance->me8254_idx == 0) {
+			if (instance->ctr_idx == 0) {
+				clk_src |= (ME1400AB_8254_A_0_CLK_SRC_QUARZ);
+				clk_src &= ~(ME1400AB_8254_A_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			if (instance->ctr_idx == 0) {
+				clk_src |= (ME1400AB_8254_B_0_CLK_SRC_QUARZ);
+				clk_src &= ~(ME1400AB_8254_B_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		}
+
+		break;
+
+	case ME_REF_CTR_INTERNAL_10MHZ:
+		if (instance->me8254_idx == 0) {
+			if (instance->ctr_idx == 0) {
+				clk_src |= (ME1400AB_8254_A_0_CLK_SRC_QUARZ);
+				clk_src |= (ME1400AB_8254_A_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		} else {
+			if (instance->ctr_idx == 0) {
+				clk_src |= (ME1400AB_8254_A_0_CLK_SRC_QUARZ);
+				clk_src |= (ME1400AB_8254_A_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_SINGLE_CONFIG;
+			}
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid reference.\n");
+		spin_unlock(instance->clk_src_reg_lock);
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	outb(clk_src, instance->clk_src_reg);
+	spin_unlock(instance->clk_src_reg_lock);
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me1400_cd_ref_config(me8254_subdevice_t * instance, int ref)
+{
+	uint8_t clk_src;
+
+	spin_lock(instance->clk_src_reg_lock);
+	clk_src = inb(instance->clk_src_reg);
+
+	switch (ref) {
+	case ME_REF_CTR_EXTERNAL:
+		switch (instance->me8254_idx) {
+		case 0:
+		case 2:
+		case 4:
+		case 6:
+		case 8:
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400CD_8254_ACE_0_CLK_SRC_MASK);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400CD_8254_ACE_1_CLK_SRC_MASK);
+			else
+				clk_src &= ~(ME1400CD_8254_ACE_2_CLK_SRC_MASK);
+			break;
+
+		default:
+			if (instance->ctr_idx == 0)
+				clk_src &= ~(ME1400CD_8254_BD_0_CLK_SRC_MASK);
+			else if (instance->ctr_idx == 1)
+				clk_src &= ~(ME1400CD_8254_BD_1_CLK_SRC_MASK);
+			else
+				clk_src &= ~(ME1400CD_8254_BD_2_CLK_SRC_MASK);
+			break;
+		}
+		break;
+
+	case ME_REF_CTR_PREVIOUS:
+		switch (instance->me8254_idx) {
+		case 0:
+		case 2:
+		case 4:
+		case 6:
+		case 8:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_ACE_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_ACE_0_CLK_SRC_PREV);
+			} else if (instance->ctr_idx == 1) {
+				clk_src &= ~(ME1400CD_8254_ACE_1_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_ACE_1_CLK_SRC_PREV);
+			} else {
+				clk_src &= ~(ME1400CD_8254_ACE_2_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_ACE_2_CLK_SRC_PREV);
+			}
+			break;
+
+		default:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_BD_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_BD_0_CLK_SRC_PREV);
+			} else if (instance->ctr_idx == 1) {
+				clk_src &= ~(ME1400CD_8254_BD_1_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_BD_1_CLK_SRC_PREV);
+			} else {
+				clk_src &= ~(ME1400CD_8254_BD_2_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_BD_2_CLK_SRC_PREV);
+			}
+			break;
+		}
+
+		break;
+
+	case ME_REF_CTR_INTERNAL_1MHZ:
+		switch (instance->me8254_idx) {
+		case 0:
+		case 2:
+		case 4:
+		case 6:
+		case 8:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_ACE_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_ACE_0_CLK_SRC_1MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_REF;
+			}
+
+			break;
+
+		default:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_BD_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_BD_0_CLK_SRC_1MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_REF;
+			}
+			break;
+		}
+
+		break;
+
+	case ME_REF_CTR_INTERNAL_10MHZ:
+		switch (instance->me8254_idx) {
+		case 0:
+		case 2:
+		case 4:
+		case 6:
+		case 8:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_ACE_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_ACE_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_REF;
+			}
+			break;
+
+		default:
+			if (instance->ctr_idx == 0) {
+				clk_src &= ~(ME1400CD_8254_BD_0_CLK_SRC_MASK);
+				clk_src |= (ME1400CD_8254_BD_0_CLK_SRC_10MHZ);
+			} else {
+				PERROR("Invalid reference.\n");
+				spin_unlock(instance->clk_src_reg_lock);
+				return ME_ERRNO_INVALID_REF;
+			}
+
+			break;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid reference.\n");
+		spin_unlock(instance->clk_src_reg_lock);
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	outb(clk_src, instance->clk_src_reg);
+	spin_unlock(instance->clk_src_reg_lock);
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me4600_ref_config(me8254_subdevice_t * instance, int ref)
+{
+	switch (ref) {
+
+	case ME_REF_CTR_EXTERNAL:
+		// Nothing to do
+		break;
+
+	default:
+		PERROR("Invalid reference.\n");
+//                      spin_unlock(instance->clk_src_reg_lock);
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8100_ref_config(me8254_subdevice_t * instance, int ref)
+{
+	switch (ref) {
+
+	case ME_REF_CTR_EXTERNAL:
+		// Nothing to do
+		break;
+
+	default:
+		PERROR("Invalid reference.\n");
+//                      spin_unlock(instance->clk_src_reg_lock);
+		return ME_ERRNO_INVALID_REF;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_io_single_config(struct me_subdevice *subdevice,
+				   struct file *filep,
+				   int channel,
+				   int single_config,
+				   int ref,
+				   int trig_chan,
+				   int trig_type, int trig_edge, int flags)
+{
+	me8254_subdevice_t *instance;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	instance = (me8254_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	// Configure the counter modes
+	if (instance->ctr_idx == 0) {
+		if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_0) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_1) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M1 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_2) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M2 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_3) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M3 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_4) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M4 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_5) {
+			outb(ME8254_CTRL_SC0 | ME8254_CTRL_LM | ME8254_CTRL_M5 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else {
+			PERROR("Invalid single configuration.\n");
+			spin_unlock(&instance->subdevice_lock);
+			return ME_ERRNO_INVALID_SINGLE_CONFIG;
+		}
+	} else if (instance->ctr_idx == 1) {
+		if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_0) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_1) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M1 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_2) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M2 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_3) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M3 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_4) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M4 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_5) {
+			outb(ME8254_CTRL_SC1 | ME8254_CTRL_LM | ME8254_CTRL_M5 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else {
+			PERROR("Invalid single configuration.\n");
+			spin_unlock(&instance->subdevice_lock);
+			return ME_ERRNO_INVALID_SINGLE_CONFIG;
+		}
+	} else {
+		if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_0) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M0 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_1) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M1 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_2) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M2 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_3) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M3 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_4) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M4 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else if (single_config == ME_SINGLE_CONFIG_CTR_8254_MODE_5) {
+			outb(ME8254_CTRL_SC2 | ME8254_CTRL_LM | ME8254_CTRL_M5 |
+			     ME8254_CTRL_BIN, instance->ctrl_reg);
+		} else {
+			PERROR("Invalid single configuration.\n");
+			spin_unlock(&instance->subdevice_lock);
+			return ME_ERRNO_INVALID_SINGLE_CONFIG;
+		}
+	}
+
+	switch (instance->device_id) {
+	case PCI_DEVICE_ID_MEILHAUS_ME1400:
+	case PCI_DEVICE_ID_MEILHAUS_ME14E0:
+	case PCI_DEVICE_ID_MEILHAUS_ME140A:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EA:
+	case PCI_DEVICE_ID_MEILHAUS_ME140B:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EB:
+		err = me1400_ab_ref_config(instance, ref);
+
+		if (err) {
+			spin_unlock(&instance->subdevice_lock);
+			return err;
+		}
+
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		err = me1400_cd_ref_config(instance, ref);
+
+		if (err) {
+			spin_unlock(&instance->subdevice_lock);
+			return err;
+		}
+
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME4610:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680IS:
+		err = me4600_ref_config(instance, ref);
+
+		if (err) {
+			spin_unlock(&instance->subdevice_lock);
+			return err;
+		}
+
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_A:
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_B:
+		err = me8100_ref_config(instance, ref);
+
+		if (err) {
+			spin_unlock(&instance->subdevice_lock);
+			return err;
+		}
+
+		break;
+
+	default:
+		PERROR("Invalid device type.\n");
+
+		spin_unlock(&instance->subdevice_lock);
+//                              spin_unlock(instance->clk_src_reg_lock);
+		return ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_io_single_read(struct me_subdevice *subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *value, int time_out, int flags)
+{
+	me8254_subdevice_t *instance;
+	uint16_t lo_byte;
+	uint16_t hi_byte;
+
+	PDEBUG("executed.\n");
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	instance = (me8254_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	if (instance->ctr_idx == 0)
+		outb(ME8254_CTRL_SC0 | ME8254_CTRL_TLO, instance->ctrl_reg);
+	else if (instance->ctr_idx == 1)
+		outb(ME8254_CTRL_SC1 | ME8254_CTRL_TLO, instance->ctrl_reg);
+	else
+		outb(ME8254_CTRL_SC2 | ME8254_CTRL_TLO, instance->ctrl_reg);
+
+	lo_byte = inb(instance->val_reg);
+	hi_byte = inb(instance->val_reg);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	*value = lo_byte | (hi_byte << 8);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_io_single_write(struct me_subdevice *subdevice,
+				  struct file *filep,
+				  int channel,
+				  int value, int time_out, int flags)
+{
+	me8254_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	instance = (me8254_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	outb(value, instance->val_reg);
+	outb((value >> 8), instance->val_reg);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_query_number_channels(struct me_subdevice *subdevice,
+					int *number)
+{
+	PDEBUG("executed.\n");
+	*number = ME8254_NUMBER_CHANNELS;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_query_subdevice_type(struct me_subdevice *subdevice,
+				       int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_CTR;
+	*subtype = ME_SUBTYPE_CTR_8254;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_query_subdevice_caps(struct me_subdevice *subdevice,
+				       int *caps)
+{
+	me8254_subdevice_t *instance;
+	PDEBUG("executed.\n");
+	instance = (me8254_subdevice_t *) subdevice;
+	*caps = instance->caps;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8254_query_subdevice_caps_args(struct me_subdevice *subdevice,
+					    int cap, int *args, int count)
+{
+	PDEBUG("executed.\n");
+
+	if (count != 1) {
+		PERROR("Invalid capability argument count.\n");
+		return ME_ERRNO_INVALID_CAP_ARG_COUNT;
+	}
+
+	if (cap == ME_CAP_CTR_WIDTH) {
+		args[0] = ME8254_CTR_WIDTH;
+	} else {
+		PERROR("Invalid capability.\n");
+		return ME_ERRNO_INVALID_CAP;
+	}
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static uint32_t me1400AB_get_val_reg(uint32_t reg_base, unsigned int me8254_idx,
+				     unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+
+	case 0:
+		return (reg_base + ME1400AB_8254_A_0_VAL_REG + ctr_idx);
+
+	default:
+		return (reg_base + ME1400AB_8254_B_0_VAL_REG + ctr_idx);
+	}
+
+	return 0;
+}
+
+static uint32_t me1400AB_get_ctrl_reg(uint32_t reg_base,
+				      unsigned int me8254_idx,
+				      unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+	case 0:
+		return (reg_base + ME1400AB_8254_A_CTRL_REG);
+
+	default:
+		return (reg_base + ME1400AB_8254_B_CTRL_REG);
+	}
+
+	return 0;
+}
+
+static uint32_t me1400AB_get_clk_src_reg(uint32_t reg_base,
+					 unsigned int me8254_idx,
+					 unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+	case 0:
+		return (reg_base + ME1400AB_CLK_SRC_REG);
+
+	default:
+		return (reg_base + ME1400AB_CLK_SRC_REG);
+	}
+
+	return 0;
+}
+
+static uint32_t me1400CD_get_val_reg(uint32_t reg_base, unsigned int me8254_idx,
+				     unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+	case 0:
+		return (reg_base + ME1400C_8254_A_0_VAL_REG + ctr_idx);
+
+	case 1:
+		return (reg_base + ME1400C_8254_B_0_VAL_REG + ctr_idx);
+
+	case 2:
+		return (reg_base + ME1400C_8254_C_0_VAL_REG + ctr_idx);
+
+	case 3:
+		return (reg_base + ME1400C_8254_D_0_VAL_REG + ctr_idx);
+
+	case 4:
+		return (reg_base + ME1400C_8254_E_0_VAL_REG + ctr_idx);
+
+	case 5:
+		return (reg_base + ME1400D_8254_A_0_VAL_REG + ctr_idx);
+
+	case 6:
+		return (reg_base + ME1400D_8254_B_0_VAL_REG + ctr_idx);
+
+	case 7:
+		return (reg_base + ME1400D_8254_C_0_VAL_REG + ctr_idx);
+
+	case 8:
+		return (reg_base + ME1400D_8254_D_0_VAL_REG + ctr_idx);
+
+	default:
+		return (reg_base + ME1400D_8254_E_0_VAL_REG + ctr_idx);
+	}
+
+	return 0;
+}
+
+static uint32_t me1400CD_get_ctrl_reg(uint32_t reg_base,
+				      unsigned int me8254_idx,
+				      unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+	case 0:
+		return (reg_base + ME1400C_8254_A_CTRL_REG);
+
+	case 1:
+		return (reg_base + ME1400C_8254_B_CTRL_REG);
+
+	case 2:
+		return (reg_base + ME1400C_8254_C_CTRL_REG);
+
+	case 3:
+		return (reg_base + ME1400C_8254_D_CTRL_REG);
+
+	case 4:
+		return (reg_base + ME1400C_8254_E_CTRL_REG);
+
+	case 5:
+		return (reg_base + ME1400D_8254_A_CTRL_REG);
+
+	case 6:
+		return (reg_base + ME1400D_8254_B_CTRL_REG);
+
+	case 7:
+		return (reg_base + ME1400D_8254_C_CTRL_REG);
+
+	case 8:
+		return (reg_base + ME1400D_8254_D_CTRL_REG);
+
+	default:
+		return (reg_base + ME1400D_8254_E_CTRL_REG);
+	}
+
+	return 0;
+}
+
+static uint32_t me1400CD_get_clk_src_reg(uint32_t reg_base,
+					 unsigned int me8254_idx,
+					 unsigned int ctr_idx)
+{
+	switch (me8254_idx) {
+	case 0:
+		return (reg_base + ME1400C_CLK_SRC_0_REG);
+
+	case 1:
+		return (reg_base + ME1400C_CLK_SRC_0_REG);
+
+	case 2:
+		return (reg_base + ME1400C_CLK_SRC_1_REG);
+
+	case 3:
+		return (reg_base + ME1400C_CLK_SRC_1_REG);
+
+	case 4:
+		return (reg_base + ME1400C_CLK_SRC_2_REG);
+
+	case 5:
+		return (reg_base + ME1400D_CLK_SRC_0_REG);
+
+	case 6:
+		return (reg_base + ME1400D_CLK_SRC_0_REG);
+
+	case 7:
+		return (reg_base + ME1400D_CLK_SRC_1_REG);
+
+	case 8:
+		return (reg_base + ME1400D_CLK_SRC_1_REG);
+
+	default:
+		return (reg_base + ME1400D_CLK_SRC_2_REG);
+	}
+
+	return 0;
+}
+
+static uint32_t me4600_get_val_reg(uint32_t reg_base, unsigned int me8254_idx,
+				   unsigned int ctr_idx)
+{
+	return (reg_base + ME4600_8254_0_VAL_REG + ctr_idx);
+}
+
+static uint32_t me4600_get_ctrl_reg(uint32_t reg_base, unsigned int me8254_idx,
+				    unsigned int ctr_idx)
+{
+	return (reg_base + ME4600_8254_CTRL_REG);
+}
+
+static uint32_t me8100_get_val_reg(uint32_t reg_base, unsigned int me8254_idx,
+				   unsigned int ctr_idx)
+{
+	return (reg_base + ME8100_COUNTER_REG_0 + ctr_idx * 2);
+}
+
+static uint32_t me8100_get_ctrl_reg(uint32_t reg_base, unsigned int me8254_idx,
+				    unsigned int ctr_idx)
+{
+	return (reg_base + ME8100_COUNTER_CTRL_REG);
+}
+
+me8254_subdevice_t *me8254_constructor(uint32_t device_id,
+				       uint32_t reg_base,
+				       unsigned int me8254_idx,
+				       unsigned int ctr_idx,
+				       spinlock_t * ctrl_reg_lock,
+				       spinlock_t * clk_src_reg_lock)
+{
+	me8254_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	// Allocate memory for subdevice instance
+	subdevice = kmalloc(sizeof(me8254_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for 8254 instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8254_subdevice_t));
+
+	// Check if counter index is out of range
+
+	if (ctr_idx > 2) {
+		PERROR("Counter index is out of range.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize subdevice base class
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+	subdevice->clk_src_reg_lock = clk_src_reg_lock;
+
+	// Save type of Meilhaus device
+	subdevice->device_id = device_id;
+
+	// Save the indices
+	subdevice->me8254_idx = me8254_idx;
+	subdevice->ctr_idx = ctr_idx;
+
+	// Do device specific initialization
+	switch (device_id) {
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140A:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EA:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 0) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140B:	// Fall through
+	case PCI_DEVICE_ID_MEILHAUS_ME14EB:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 1) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+		// Initialize the counters capabilities
+		if (ctr_idx == 0)
+			subdevice->caps =
+			    ME_CAPS_CTR_CLK_INTERNAL_1MHZ |
+			    ME_CAPS_CTR_CLK_INTERNAL_10MHZ |
+			    ME_CAPS_CTR_CLK_EXTERNAL;
+		else
+			subdevice->caps =
+			    ME_CAPS_CTR_CLK_PREVIOUS | ME_CAPS_CTR_CLK_EXTERNAL;
+
+		// Get the counters registers
+		subdevice->val_reg =
+		    me1400AB_get_val_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->ctrl_reg =
+		    me1400AB_get_ctrl_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->clk_src_reg =
+		    me1400AB_get_clk_src_reg(reg_base, me8254_idx, ctr_idx);
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 4) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 9) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+		// Initialize the counters capabilities
+		if (ctr_idx == 0) {
+			if (me8254_idx == 0)
+				subdevice->caps =
+				    ME_CAPS_CTR_CLK_PREVIOUS |
+				    ME_CAPS_CTR_CLK_INTERNAL_1MHZ |
+				    ME_CAPS_CTR_CLK_INTERNAL_10MHZ |
+				    ME_CAPS_CTR_CLK_EXTERNAL;
+			else
+				subdevice->caps =
+				    ME_CAPS_CTR_CLK_INTERNAL_1MHZ |
+				    ME_CAPS_CTR_CLK_INTERNAL_10MHZ |
+				    ME_CAPS_CTR_CLK_EXTERNAL;
+		} else
+			subdevice->caps =
+			    ME_CAPS_CTR_CLK_PREVIOUS | ME_CAPS_CTR_CLK_EXTERNAL;
+
+		// Get the counters registers
+		subdevice->val_reg =
+		    me1400CD_get_val_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->ctrl_reg =
+		    me1400CD_get_ctrl_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->clk_src_reg =
+		    me1400CD_get_clk_src_reg(reg_base, me8254_idx, ctr_idx);
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME4610:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4660IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4670IS:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680I:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680S:
+	case PCI_DEVICE_ID_MEILHAUS_ME4680IS:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 0) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+		// Initialize the counters capabilities
+		subdevice->caps = ME_CAPS_CTR_CLK_EXTERNAL;
+
+		// Get the counters registers
+		subdevice->val_reg =
+		    me4600_get_val_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->ctrl_reg =
+		    me4600_get_ctrl_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->clk_src_reg = 0;	// Not used
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_A:
+	case PCI_DEVICE_ID_MEILHAUS_ME8100_B:
+		// Check if 8254 index is out of range
+		if (me8254_idx > 0) {
+			PERROR("8254 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+		// Initialize the counters capabilities
+		subdevice->caps = ME_CAPS_CTR_CLK_EXTERNAL;
+
+		// Get the counters registers
+		subdevice->val_reg =
+		    me8100_get_val_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->ctrl_reg =
+		    me8100_get_ctrl_reg(reg_base, me8254_idx, ctr_idx);
+		subdevice->clk_src_reg = 0;	// Not used
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME4650:
+	case PCI_DEVICE_ID_MEILHAUS_ME1400:
+	case PCI_DEVICE_ID_MEILHAUS_ME14E0:
+		PERROR("No 8254 subdevices available for subdevice device.\n");
+		me_subdevice_deinit(&subdevice->base);
+		kfree(subdevice);
+		return NULL;
+
+	default:
+		PERROR("Unknown device type.\n");
+		me_subdevice_deinit(&subdevice->base);
+		kfree(subdevice);
+		return NULL;
+	}
+
+	// Overload subdevice base class methods.
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8254_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config = me8254_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8254_io_single_read;
+	subdevice->base.me_subdevice_io_single_write = me8254_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8254_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8254_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8254_query_subdevice_caps;
+	subdevice->base.me_subdevice_query_subdevice_caps_args =
+	    me8254_query_subdevice_caps_args;
+
+	return subdevice;
+}

+ 80 - 0
drivers/staging/meilhaus/me8254.h

@@ -0,0 +1,80 @@
+/**
+ * @file me8254.h
+ *
+ * @brief 8254 counter implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _ME8254_H_
+#define _ME8254_H_
+
+#include "mesubdevice.h"
+#include "meslock.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The 8254 subdevice class.
+ */
+typedef struct me8254_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect the control register from concurrent access. */
+	spinlock_t *clk_src_reg_lock;		/**< Spin lock to protect the clock source register from concurrent access. */
+
+	uint32_t device_id;			/**< The Meilhaus device type carrying the 8254 chip. */
+	int me8254_idx;				/**< The index of the 8254 chip on the device. */
+	int ctr_idx;				/**< The index of the counter on the 8254 chip. */
+
+	int caps;				/**< Holds the device capabilities. */
+
+	unsigned long val_reg;			/**< Holds the actual counter value. */
+	unsigned long ctrl_reg;			/**< Register to configure the 8254 modes. */
+	unsigned long clk_src_reg;		/**< Register to configure the counter connections. */
+} me8254_subdevice_t;
+
+/**
+ * @brief The constructor to generate a 8254 instance.
+ *
+ * @param device_id The kind of Meilhaus device holding the 8254.
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param me8254_idx The index of the 8254 chip on the Meilhaus device.
+ * @param ctr_idx The index of the counter inside a 8254 chip.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the 8254 control register from concurrent access.
+ * @param clk_src_reg_lock Pointer to spin lock protecting the clock source register from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8254_subdevice_t *me8254_constructor(uint32_t device_id,
+				       uint32_t reg_base,
+				       unsigned int me8254_idx,
+				       unsigned int ctr_idx,
+				       spinlock_t * ctrl_reg_lock,
+				       spinlock_t * clk_src_reg_lock);
+
+#endif
+#endif

+ 172 - 0
drivers/staging/meilhaus/me8254_reg.h

@@ -0,0 +1,172 @@
+/**
+ * @file me8254_reg.h
+ *
+ * @brief 8254 counter register definitions.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME8254_REG_H_
+#define _ME8254_REG_H_
+
+#ifdef __KERNEL__
+
+/* ME1400 A/B register offsets */
+#define ME1400AB_8254_A_0_VAL_REG		0x0004 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400AB_8254_A_1_VAL_REG		0x0005 /**< Offset of 8254 A counter 1 value register. */
+#define ME1400AB_8254_A_2_VAL_REG		0x0006 /**< Offset of 8254 A counter 2 value register. */
+#define ME1400AB_8254_A_CTRL_REG		0x0007 /**< Offset of 8254 A control register. */
+
+#define ME1400AB_8254_B_0_VAL_REG		0x000C /**< Offset of 8254 B counter 0 value register. */
+#define ME1400AB_8254_B_1_VAL_REG		0x000D /**< Offset of 8254 B counter 1 value register. */
+#define ME1400AB_8254_B_2_VAL_REG		0x000E /**< Offset of 8254 B counter 2 value register. */
+#define ME1400AB_8254_B_CTRL_REG		0x000F /**< Offset of 8254 B control register. */
+
+#define ME1400AB_CLK_SRC_REG			0x0010 /**< Offset of clock source register. */
+
+/* ME1400 C register offsets */
+#define ME1400C_8254_A_0_VAL_REG		0x0004 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400C_8254_A_1_VAL_REG		0x0005 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400C_8254_A_2_VAL_REG		0x0006 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400C_8254_A_CTRL_REG			0x0007 /**< Offset of 8254 A control register. */
+
+#define ME1400C_8254_B_0_VAL_REG		0x000C /**< Offset of 8254 B counter 0 value register. */
+#define ME1400C_8254_B_1_VAL_REG		0x000D /**< Offset of 8254 B counter 0 value register. */
+#define ME1400C_8254_B_2_VAL_REG		0x000E /**< Offset of 8254 B counter 0 value register. */
+#define ME1400C_8254_B_CTRL_REG			0x000F /**< Offset of 8254 B control register. */
+
+#define ME1400C_8254_C_0_VAL_REG		0x0010 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400C_8254_C_1_VAL_REG		0x0011 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400C_8254_C_2_VAL_REG		0x0012 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400C_8254_C_CTRL_REG			0x0013 /**< Offset of 8254 C control register. */
+
+#define ME1400C_8254_D_0_VAL_REG		0x0014 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400C_8254_D_1_VAL_REG		0x0015 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400C_8254_D_2_VAL_REG		0x0016 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400C_8254_D_CTRL_REG			0x0017 /**< Offset of 8254 D control register. */
+
+#define ME1400C_8254_E_0_VAL_REG		0x0018 /**< Offset of 8254 E counter 0 value register. */
+#define ME1400C_8254_E_1_VAL_REG		0x0019 /**< Offset of 8254 E counter 0 value register. */
+#define ME1400C_8254_E_2_VAL_REG		0x001A /**< Offset of 8254 E counter 0 value register. */
+#define ME1400C_8254_E_CTRL_REG			0x001B /**< Offset of 8254 E control register. */
+
+#define ME1400C_CLK_SRC_0_REG  			0x001C /**< Offset of clock source register 0. */
+#define ME1400C_CLK_SRC_1_REG  			0x001D /**< Offset of clock source register 1. */
+#define ME1400C_CLK_SRC_2_REG  			0x001E /**< Offset of clock source register 2. */
+
+/* ME1400 D register offsets */
+#define ME1400D_8254_A_0_VAL_REG		0x0044 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400D_8254_A_1_VAL_REG		0x0045 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400D_8254_A_2_VAL_REG		0x0046 /**< Offset of 8254 A counter 0 value register. */
+#define ME1400D_8254_A_CTRL_REG			0x0047 /**< Offset of 8254 A control register. */
+
+#define ME1400D_8254_B_0_VAL_REG		0x004C /**< Offset of 8254 B counter 0 value register. */
+#define ME1400D_8254_B_1_VAL_REG		0x004D /**< Offset of 8254 B counter 0 value register. */
+#define ME1400D_8254_B_2_VAL_REG		0x004E /**< Offset of 8254 B counter 0 value register. */
+#define ME1400D_8254_B_CTRL_REG			0x004F /**< Offset of 8254 B control register. */
+
+#define ME1400D_8254_C_0_VAL_REG		0x0050 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400D_8254_C_1_VAL_REG		0x0051 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400D_8254_C_2_VAL_REG		0x0052 /**< Offset of 8254 C counter 0 value register. */
+#define ME1400D_8254_C_CTRL_REG			0x0053 /**< Offset of 8254 C control register. */
+
+#define ME1400D_8254_D_0_VAL_REG		0x0054 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400D_8254_D_1_VAL_REG		0x0055 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400D_8254_D_2_VAL_REG		0x0056 /**< Offset of 8254 D counter 0 value register. */
+#define ME1400D_8254_D_CTRL_REG			0x0057 /**< Offset of 8254 D control register. */
+
+#define ME1400D_8254_E_0_VAL_REG		0x0058 /**< Offset of 8254 E counter 0 value register. */
+#define ME1400D_8254_E_1_VAL_REG		0x0059 /**< Offset of 8254 E counter 0 value register. */
+#define ME1400D_8254_E_2_VAL_REG		0x005A /**< Offset of 8254 E counter 0 value register. */
+#define ME1400D_8254_E_CTRL_REG			0x005B /**< Offset of 8254 E control register. */
+
+#define ME1400D_CLK_SRC_0_REG  			0x005C /**< Offset of clock source register 0. */
+#define ME1400D_CLK_SRC_1_REG  			0x005D /**< Offset of clock source register 1. */
+#define ME1400D_CLK_SRC_2_REG  			0x005E /**< Offset of clock source register 2. */
+
+/* ME4600 register offsets */
+#define ME4600_8254_0_VAL_REG			0x0000 /**< Offset of 8254 A counter 0 value register. */
+#define ME4600_8254_1_VAL_REG			0x0001 /**< Offset of 8254 A counter 0 value register. */
+#define ME4600_8254_2_VAL_REG			0x0002 /**< Offset of 8254 A counter 0 value register. */
+#define ME4600_8254_CTRL_REG			0x0003 /**< Offset of 8254 A control register. */
+
+/* Command words for 8254 control register */
+#define ME8254_CTRL_SC0   0x00	 /**< Counter 0 selection. */
+#define ME8254_CTRL_SC1   0x40	 /**< Counter 1 selection. */
+#define ME8254_CTRL_SC2   0x80	 /**< Counter 2 selection. */
+
+#define ME8254_CTRL_TLO   0x00	 /**< Counter latching operation. */
+#define ME8254_CTRL_LSB   0x10	 /**< Only read LSB. */
+#define ME8254_CTRL_MSB   0x20	 /**< Only read MSB. */
+#define ME8254_CTRL_LM    0x30	 /**< First read LSB, then MSB.  */
+
+#define ME8254_CTRL_M0    0x00	 /**< Mode 0 selection. */
+#define ME8254_CTRL_M1    0x02	 /**< Mode 1 selection. */
+#define ME8254_CTRL_M2    0x04	 /**< Mode 2 selection. */
+#define ME8254_CTRL_M3    0x06	 /**< Mode 3 selection. */
+#define ME8254_CTRL_M4    0x08	 /**< Mode 4 selection. */
+#define ME8254_CTRL_M5    0x0A	 /**< Mode 5 selection. */
+
+#define ME8254_CTRL_BIN   0x00	 /**< Binary counter. */
+#define ME8254_CTRL_BCD   0x01	 /**< BCD counter. */
+
+/* ME-1400 A/B clock source register bits */
+#define ME1400AB_8254_A_0_CLK_SRC_1MHZ		(0 << 7)	/**< 1MHz clock. */
+#define ME1400AB_8254_A_0_CLK_SRC_10MHZ		(1 << 7)	/**< 10MHz clock. */
+#define ME1400AB_8254_A_0_CLK_SRC_PIN		(0 << 6)	/**< CLK 0 to SUB-D. */
+#define ME1400AB_8254_A_0_CLK_SRC_QUARZ		(1 << 6)	/**< Connect CLK 0 with quarz. */
+
+#define ME1400AB_8254_A_1_CLK_SRC_PIN		(0 << 5)	/**< CLK 1 to SUB-D. */
+#define ME1400AB_8254_A_1_CLK_SRC_PREV		(1 << 5)	/**< Connect OUT 0 with CLK 1. */
+
+#define ME1400AB_8254_A_2_CLK_SRC_PIN		(0 << 4)	/**< CLK 2 to SUB-D. */
+#define ME1400AB_8254_A_2_CLK_SRC_PREV		(1 << 4)	/**< Connect OUT 1 with CLK 2. */
+
+#define ME1400AB_8254_B_0_CLK_SRC_1MHZ		(0 << 3)	/**< 1MHz clock. */
+#define ME1400AB_8254_B_0_CLK_SRC_10MHZ		(1 << 3)	/**< 10MHz clock. */
+#define ME1400AB_8254_B_0_CLK_SRC_PIN		(0 << 2)	/**< CLK 0 to SUB-D. */
+#define ME1400AB_8254_B_0_CLK_SRC_QUARZ		(1 << 2)	/**< Connect CLK 0 with quarz. */
+
+#define ME1400AB_8254_B_1_CLK_SRC_PIN		(0 << 1)	/**< CLK 1 to SUB-D. */
+#define ME1400AB_8254_B_1_CLK_SRC_PREV		(1 << 1)	/**< Connect OUT 0 with CLK 1. */
+
+#define ME1400AB_8254_B_2_CLK_SRC_PIN		(0 << 0)	/**< CLK 2 to SUB-D. */
+#define ME1400AB_8254_B_2_CLK_SRC_PREV		(1 << 0)	/**< Connect OUT 1 with CLK 2. */
+
+/* ME-1400 C/D clock source registers bits */
+#define ME1400CD_8254_ACE_0_CLK_SRC_MASK	0x03	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_ACE_0_CLK_SRC_PIN		0x00	/**< Connect CLK to SUB-D. */
+#define ME1400CD_8254_ACE_0_CLK_SRC_1MHZ	0x01	/**< Connect CLK to 1MHz. */
+#define ME1400CD_8254_ACE_0_CLK_SRC_10MHZ	0x02	/**< Connect CLK to 10MHz. */
+#define ME1400CD_8254_ACE_0_CLK_SRC_PREV	0x03	/**< Connect CLK to previous counter output on ME-1400 D extension. */
+
+#define ME1400CD_8254_ACE_1_CLK_SRC_MASK	0x04	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_ACE_1_CLK_SRC_PIN		0x00	/**< Connect CLK to SUB-D. */
+#define ME1400CD_8254_ACE_1_CLK_SRC_PREV	0x04	/**< Connect CLK to previous counter output. */
+
+#define ME1400CD_8254_ACE_2_CLK_SRC_MASK	0x08	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_ACE_2_CLK_SRC_PIN		0x00	/**< Connect to SUB-D. */
+#define ME1400CD_8254_ACE_2_CLK_SRC_PREV	0x08	/**< Connect CLK to previous counter output. */
+
+#define ME1400CD_8254_BD_0_CLK_SRC_MASK		0x30	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_BD_0_CLK_SRC_PIN		0x00	/**< Connect CLK to SUB-D. */
+#define ME1400CD_8254_BD_0_CLK_SRC_1MHZ		0x10	/**< Connect CLK to 1MHz. */
+#define ME1400CD_8254_BD_0_CLK_SRC_10MHZ	0x20	/**< Connect CLK to 10MHz. */
+#define ME1400CD_8254_BD_0_CLK_SRC_PREV		0x30	/**< Connect CLK to previous counter output. */
+
+#define ME1400CD_8254_BD_1_CLK_SRC_MASK		0x40	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_BD_1_CLK_SRC_PIN		0x00	/**< Connect CLK to SUB-D. */
+#define ME1400CD_8254_BD_1_CLK_SRC_PREV		0x40	/**< Connect CLK to previous counter output. */
+
+#define ME1400CD_8254_BD_2_CLK_SRC_MASK		0x80	/**< Masks all CLK source bits. */
+#define ME1400CD_8254_BD_2_CLK_SRC_PIN		0x00	/**< Connect CLK to SUB-D. */
+#define ME1400CD_8254_BD_2_CLK_SRC_PREV		0x80	/**< Connect CLK to previous counter output. */
+
+/* ME-8100 counter registers */
+#define ME8100_COUNTER_REG_0				0x18	//(r,w)
+#define ME8100_COUNTER_REG_1				0x1A	//(r,w)
+#define ME8100_COUNTER_REG_2				0x1C	//(r,w)
+#define ME8100_COUNTER_CTRL_REG				0x1E	//(r,w)
+
+#endif
+#endif

+ 462 - 0
drivers/staging/meilhaus/me8255.c

@@ -0,0 +1,462 @@
+/**
+ * @file me8255.c
+ *
+ * @brief 8255 subdevice instance.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef __KERNEL__
+#  define __KERNEL__
+#endif
+
+/*
+ * Includes
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <linux/types.h>
+
+#include "medefines.h"
+#include "meinternal.h"
+#include "meerror.h"
+#include "medebug.h"
+
+#include "me8255_reg.h"
+#include "me8255.h"
+
+/*
+ * Defines
+ */
+
+/*
+ * Functions
+ */
+
+static uint8_t get_mode_from_mirror(uint32_t mirror)
+{
+	PDEBUG("executed.\n");
+
+	if (mirror & ME8255_PORT_0_OUTPUT) {
+		if (mirror & ME8255_PORT_1_OUTPUT) {
+			if (mirror & ME8255_PORT_2_OUTPUT) {
+				return ME8255_MODE_OOO;
+			} else {
+				return ME8255_MODE_IOO;
+			}
+		} else {
+			if (mirror & ME8255_PORT_2_OUTPUT) {
+				return ME8255_MODE_OIO;
+			} else {
+				return ME8255_MODE_IIO;
+			}
+		}
+	} else {
+		if (mirror & ME8255_PORT_1_OUTPUT) {
+			if (mirror & ME8255_PORT_2_OUTPUT) {
+				return ME8255_MODE_OOI;
+			} else {
+				return ME8255_MODE_IOI;
+			}
+		} else {
+			if (mirror & ME8255_PORT_2_OUTPUT) {
+				return ME8255_MODE_OII;
+			} else {
+				return ME8255_MODE_III;
+			}
+		}
+	}
+}
+
+static int me8255_io_reset_subdevice(struct me_subdevice *subdevice,
+				     struct file *filep, int flags)
+{
+	me8255_subdevice_t *instance;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8255_subdevice_t *) subdevice;
+
+	if (flags) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	spin_lock(instance->ctrl_reg_lock);
+	*instance->ctrl_reg_mirror &=
+	    ~(ME8255_PORT_0_OUTPUT << instance->dio_idx);
+	outb(get_mode_from_mirror(*instance->ctrl_reg_mirror),
+	     instance->ctrl_reg);
+	spin_unlock(instance->ctrl_reg_lock);
+
+	outb(0, instance->port_reg);
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8255_io_single_config(struct me_subdevice *subdevice,
+				   struct file *filep,
+				   int channel,
+				   int single_config,
+				   int ref,
+				   int trig_chan,
+				   int trig_type, int trig_edge, int flags)
+{
+	me8255_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8255_subdevice_t *) subdevice;
+
+	if (flags & ~ME_IO_SINGLE_CONFIG_DIO_BYTE) {
+		PERROR("Invalid flag specified.\n");
+		return ME_ERRNO_INVALID_FLAGS;
+	}
+
+	if (channel) {
+		PERROR("Invalid channel.\n");
+		return ME_ERRNO_INVALID_CHANNEL;
+	}
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	if (single_config == ME_SINGLE_CONFIG_DIO_INPUT) {
+		spin_lock(instance->ctrl_reg_lock);
+		*instance->ctrl_reg_mirror &=
+		    ~(ME8255_PORT_0_OUTPUT << instance->dio_idx);
+		outb(get_mode_from_mirror(*instance->ctrl_reg_mirror),
+		     instance->ctrl_reg);
+		spin_unlock(instance->ctrl_reg_lock);
+	} else if (single_config == ME_SINGLE_CONFIG_DIO_OUTPUT) {
+		spin_lock(instance->ctrl_reg_lock);
+		*instance->ctrl_reg_mirror |=
+		    (ME8255_PORT_0_OUTPUT << instance->dio_idx);
+		outb(get_mode_from_mirror(*instance->ctrl_reg_mirror),
+		     instance->ctrl_reg);
+		spin_unlock(instance->ctrl_reg_lock);
+	} else {
+		PERROR("Invalid port direction.\n");
+		err = ME_ERRNO_INVALID_SINGLE_CONFIG;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8255_io_single_read(struct me_subdevice *subdevice,
+				 struct file *filep,
+				 int channel,
+				 int *value, int time_out, int flags)
+{
+	me8255_subdevice_t *instance;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8255_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			*value = inb(instance->port_reg) & (0x1 << channel);
+		} else {
+			PERROR("Invalid bit number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			*value = inb(instance->port_reg);
+		} else {
+			PERROR("Invalid byte number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8255_io_single_write(struct me_subdevice *subdevice,
+				  struct file *filep,
+				  int channel,
+				  int value, int time_out, int flags)
+{
+	me8255_subdevice_t *instance;
+	uint8_t byte;
+	int err = ME_ERRNO_SUCCESS;
+
+	PDEBUG("executed.\n");
+
+	instance = (me8255_subdevice_t *) subdevice;
+
+	ME_SUBDEVICE_ENTER;
+
+	spin_lock(&instance->subdevice_lock);
+	switch (flags) {
+	case ME_IO_SINGLE_TYPE_DIO_BIT:
+		if ((channel >= 0) && (channel < 8)) {
+			if (*instance->
+			    ctrl_reg_mirror & (ME8255_PORT_0_OUTPUT <<
+					       instance->dio_idx)) {
+				byte = inb(instance->port_reg);
+
+				if (value)
+					byte |= 0x1 << channel;
+				else
+					byte &= ~(0x1 << channel);
+
+				outb(byte, instance->port_reg);
+			} else {
+				PERROR("Port not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid bit number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	case ME_IO_SINGLE_NO_FLAGS:
+	case ME_IO_SINGLE_TYPE_DIO_BYTE:
+		if (channel == 0) {
+			if (*instance->
+			    ctrl_reg_mirror & (ME8255_PORT_0_OUTPUT <<
+					       instance->dio_idx)) {
+				outb(value, instance->port_reg);
+			} else {
+				PERROR("Port not in output mode.\n");
+				err = ME_ERRNO_PREVIOUS_CONFIG;
+			}
+		} else {
+			PERROR("Invalid byte number.\n");
+			err = ME_ERRNO_INVALID_CHANNEL;
+		}
+		break;
+
+	default:
+		PERROR("Invalid flags specified.\n");
+		err = ME_ERRNO_INVALID_FLAGS;
+	}
+	spin_unlock(&instance->subdevice_lock);
+
+	ME_SUBDEVICE_EXIT;
+
+	return err;
+}
+
+static int me8255_query_number_channels(struct me_subdevice *subdevice,
+					int *number)
+{
+	PDEBUG("executed.\n");
+	*number = ME8255_NUMBER_CHANNELS;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8255_query_subdevice_type(struct me_subdevice *subdevice,
+				       int *type, int *subtype)
+{
+	PDEBUG("executed.\n");
+	*type = ME_TYPE_DIO;
+	*subtype = ME_SUBTYPE_SINGLE;
+	return ME_ERRNO_SUCCESS;
+}
+
+static int me8255_query_subdevice_caps(struct me_subdevice *subdevice,
+				       int *caps)
+{
+	PDEBUG("executed.\n");
+	*caps = ME_CAPS_DIO_DIR_BYTE;
+	return ME_ERRNO_SUCCESS;
+}
+
+me8255_subdevice_t *me8255_constructor(uint32_t device_id,
+				       uint32_t reg_base,
+				       unsigned int me8255_idx,
+				       unsigned int dio_idx,
+				       int *ctrl_reg_mirror,
+				       spinlock_t * ctrl_reg_lock)
+{
+	me8255_subdevice_t *subdevice;
+	int err;
+
+	PDEBUG("executed.\n");
+
+	/* Allocate memory for subdevice instance */
+	subdevice = kmalloc(sizeof(me8255_subdevice_t), GFP_KERNEL);
+
+	if (!subdevice) {
+		PERROR("Cannot get memory for 8255 instance.\n");
+		return NULL;
+	}
+
+	memset(subdevice, 0, sizeof(me8255_subdevice_t));
+
+	/* Check if counter index is out of range */
+
+	if (dio_idx > 2) {
+		PERROR("DIO index is out of range.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+
+	/* Initialize subdevice base class */
+	err = me_subdevice_init(&subdevice->base);
+
+	if (err) {
+		PERROR("Cannot initialize subdevice base class instance.\n");
+		kfree(subdevice);
+		return NULL;
+	}
+	// Initialize spin locks.
+	spin_lock_init(&subdevice->subdevice_lock);
+
+	subdevice->ctrl_reg_lock = ctrl_reg_lock;
+
+	/* Save the pointer to global port settings */
+	subdevice->ctrl_reg_mirror = ctrl_reg_mirror;
+
+	/* Save type of Meilhaus device */
+	subdevice->device_id = device_id;
+
+	/* Save the indices */
+	subdevice->me8255_idx = me8255_idx;
+	subdevice->dio_idx = dio_idx;
+
+	/* Do device specific initialization */
+	switch (device_id) {
+	case PCI_DEVICE_ID_MEILHAUS_ME1400:
+	case PCI_DEVICE_ID_MEILHAUS_ME14E0:
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140A:
+	case PCI_DEVICE_ID_MEILHAUS_ME14EA:
+		/* Check if 8255 index is out of range */
+		if (me8255_idx > 0) {
+			PERROR("8255 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140B:	/* Fall through */
+	case PCI_DEVICE_ID_MEILHAUS_ME14EB:
+		/* Check if 8255 index is out of range */
+		if (me8255_idx > 1) {
+			PERROR("8255 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+		/* Get the registers */
+		if (me8255_idx == 0) {
+			subdevice->ctrl_reg = reg_base + ME1400AB_PORT_A_CTRL;
+			subdevice->port_reg =
+			    reg_base + ME1400AB_PORT_A_0 + dio_idx;
+		} else if (me8255_idx == 1) {
+			subdevice->ctrl_reg = reg_base + ME1400AB_PORT_B_CTRL;
+			subdevice->port_reg =
+			    reg_base + ME1400AB_PORT_B_0 + dio_idx;
+		}
+
+		break;
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140C:
+		/* Check if 8255 index is out of range */
+		if (me8255_idx > 0) {
+			PERROR("8255 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+	case PCI_DEVICE_ID_MEILHAUS_ME140D:	/* Fall through */
+		/* Check if 8255 index is out of range */
+		if (me8255_idx > 1) {
+			PERROR("8255 index is out of range.\n");
+			me_subdevice_deinit(&subdevice->base);
+			kfree(subdevice);
+			return NULL;
+		}
+
+		/* Get the registers */
+		if (me8255_idx == 0) {
+			subdevice->ctrl_reg = reg_base + ME1400CD_PORT_A_CTRL;
+			subdevice->port_reg =
+			    reg_base + ME1400CD_PORT_A_0 + dio_idx;
+		} else if (me8255_idx == 1) {
+			subdevice->ctrl_reg = reg_base + ME1400CD_PORT_B_CTRL;
+			subdevice->port_reg =
+			    reg_base + ME1400CD_PORT_B_0 + dio_idx;
+		}
+
+		break;
+
+	default:
+		PERROR("Unknown device type. dev ID: 0x%04x\n", device_id);
+
+		me_subdevice_deinit(&subdevice->base);
+
+		kfree(subdevice);
+
+		return NULL;
+	}
+
+	/* Overload subdevice base class methods. */
+	subdevice->base.me_subdevice_io_reset_subdevice =
+	    me8255_io_reset_subdevice;
+	subdevice->base.me_subdevice_io_single_config = me8255_io_single_config;
+	subdevice->base.me_subdevice_io_single_read = me8255_io_single_read;
+	subdevice->base.me_subdevice_io_single_write = me8255_io_single_write;
+	subdevice->base.me_subdevice_query_number_channels =
+	    me8255_query_number_channels;
+	subdevice->base.me_subdevice_query_subdevice_type =
+	    me8255_query_subdevice_type;
+	subdevice->base.me_subdevice_query_subdevice_caps =
+	    me8255_query_subdevice_caps;
+
+	return subdevice;
+}

+ 59 - 0
drivers/staging/meilhaus/me8255.h

@@ -0,0 +1,59 @@
+/**
+ * @file me8255.h
+ *
+ * @brief Meilhaus PIO 8255 implementation.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME8255_H_
+#define _ME8255_H_
+
+#include "mesubdevice.h"
+#include "meslock.h"
+
+#ifdef __KERNEL__
+
+/**
+ * @brief The 8255 subdevice class.
+ */
+typedef struct me8255_subdevice {
+	/* Inheritance */
+	me_subdevice_t base;			/**< The subdevice base class. */
+
+	/* Attributes */
+	spinlock_t subdevice_lock;		/**< Spin lock to protect the subdevice from concurrent access. */
+
+	int *ctrl_reg_mirror;			/**< Pointer to mirror of the control register. */
+	spinlock_t *ctrl_reg_lock;		/**< Spin lock to protect #ctrl_reg and #ctrl_reg_mirror from concurrent access. */
+
+	uint32_t device_id;				/**< The PCI device id of the device holding the 8255 chip. */
+	int me8255_idx;					/**< The index of the 8255 chip on the device. */
+	int dio_idx;					/**< The index of the DIO port on the 8255 chip. */
+
+	unsigned long port_reg;			/**< Register to read or write a value from or to the port respectively. */
+	unsigned long ctrl_reg;			/**< Register to configure the 8255 modes. */
+} me8255_subdevice_t;
+
+/**
+ * @brief The constructor to generate a 8255 instance.
+ *
+ * @param device_id The kind of Meilhaus device holding the 8255.
+ * @param reg_base The register base address of the device as returned by the PCI BIOS.
+ * @param me8255_idx The index of the 8255 chip on the Meilhaus device.
+ * @param dio_idx The index of the counter inside a 8255 chip.
+ * @param ctr_reg_mirror Pointer to mirror of control register.
+ * @param ctrl_reg_lock Pointer to spin lock protecting the 8255 control register and #ctrl_reg_mirror from concurrent access.
+ *
+ * @return Pointer to new instance on success.\n
+ * NULL on error.
+ */
+me8255_subdevice_t *me8255_constructor(uint32_t device_id,
+				       uint32_t reg_base,
+				       unsigned int me8255_idx,
+				       unsigned int dio_idx,
+				       int *ctrl_reg_mirror,
+				       spinlock_t * ctrl_reg_lock);
+
+#endif
+#endif

+ 50 - 0
drivers/staging/meilhaus/me8255_reg.h

@@ -0,0 +1,50 @@
+/**
+ * @file me8255_reg.h
+ *
+ * @brief 8255 counter register definitions.
+ * @note Copyright (C) 2006 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ */
+
+#ifndef _ME8255_REG_H_
+#define _ME8255_REG_H_
+
+#ifdef __KERNEL__
+
+#define ME8255_NUMBER_CHANNELS		8		/**< The number of channels per 8255 port. */
+
+#define ME1400AB_PORT_A_0			0x0000	/**< Port 0 offset. */
+#define ME1400AB_PORT_A_1			0x0001	/**< Port 1 offset. */
+#define ME1400AB_PORT_A_2			0x0002	/**< Port 2 offset. */
+#define ME1400AB_PORT_A_CTRL		0x0003	/**< Control register for 8255 A. */
+
+#define ME1400AB_PORT_B_0			0x0008	/**< Port 0 offset. */
+#define ME1400AB_PORT_B_1			0x0009	/**< Port 1 offset. */
+#define ME1400AB_PORT_B_2			0x000A	/**< Port 2 offset. */
+#define ME1400AB_PORT_B_CTRL		0x000B	/**< Control register for 8255 B. */
+
+#define ME1400CD_PORT_A_0			0x0000	/**< Port 0 offset. */
+#define ME1400CD_PORT_A_1			0x0001	/**< Port 1 offset. */
+#define ME1400CD_PORT_A_2			0x0002	/**< Port 2 offset. */
+#define ME1400CD_PORT_A_CTRL		0x0003	/**< Control register for 8255 A. */
+
+#define ME1400CD_PORT_B_0			0x0040	/**< Port 0 offset. */
+#define ME1400CD_PORT_B_1			0x0041	/**< Port 1 offset. */
+#define ME1400CD_PORT_B_2			0x0042	/**< Port 2 offset. */
+#define ME1400CD_PORT_B_CTRL		0x0043	/**< Control register for 8255 B. */
+
+#define ME8255_MODE_OOO				0x80	/**< Port 2 = Output, Port 1 = Output, Port 0 = Output */
+#define ME8255_MODE_IOO				0x89	/**< Port 2 = Input,  Port 1 = Output, Port 0 = Output */
+#define ME8255_MODE_OIO				0x82	/**< Port 2 = Output, Port 1 = Input,  Port 0 = Output */
+#define ME8255_MODE_IIO				0x8B	/**< Port 2 = Input,  Port 1 = Input,  Port 0 = Output */
+#define ME8255_MODE_OOI				0x90	/**< Port 2 = Output, Port 1 = Output, Port 0 = Input */
+#define ME8255_MODE_IOI				0x99	/**< Port 2 = Input,  Port 1 = Output, Port 0 = Input */
+#define ME8255_MODE_OII				0x92	/**< Port 2 = Output, Port 1 = Input,  Port 0 = Input */
+#define ME8255_MODE_III				0x9B	/**< Port 2 = Input,  Port 1 = Input,  Port 0 = Input */
+
+#define ME8255_PORT_0_OUTPUT		0x1		/**< If set in mirror then port 0 is in output mode. */
+#define ME8255_PORT_1_OUTPUT		0x2		/**< If set in mirror then port 1 is in output mode. */
+#define ME8255_PORT_2_OUTPUT		0x4		/**< If set in mirror then port 2 is in output mode. */
+
+#endif
+#endif

+ 131 - 0
drivers/staging/meilhaus/mecirc_buf.h

@@ -0,0 +1,131 @@
+/**
+ * @file mecirc_buf.h
+ *
+ * @brief Meilhaus circular buffer implementation.
+ * @note Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ * @author Guenter Gebhardt
+ * @author Krzysztof Gantzke  (k.gantzke@meilhaus.de)
+ */
+
+/*
+ * Copyright (C) 2007 Meilhaus Electronic GmbH (support@meilhaus.de)
+ *
+ * This file 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.
+ */
+
+#ifndef _MECIRC_BUF_H_
+#define _MECIRC_BUF_H_
+
+# ifdef __KERNEL__
+
+#  ifdef BOSCH
+
+typedef struct me_circ_buf {
+	unsigned int mask;
+//      unsigned int count;
+	uint32_t *buf;
+	int volatile head;
+	int volatile tail;
+} me_circ_buf_t;
+
+static int inline me_circ_buf_values(me_circ_buf_t * buf)
+{
+//      return ((buf->head - buf->tail) & (buf->count - 1));
+	return ((buf->head - buf->tail) & (buf->mask));
+}
+
+static int inline me_circ_buf_space(me_circ_buf_t * buf)
+{
+//      return ((buf->tail - (buf->head + 1)) & (buf->count - 1));
+	return ((buf->tail - (buf->head + 1)) & (buf->mask));
+}
+
+static int inline me_circ_buf_values_to_end(me_circ_buf_t * buf)
+{
+	int end;
+	int n;
+//      end = buf->count - buf->tail;
+//      n = (buf->head + end) & (buf->count - 1);
+	end = buf->mask + 1 - buf->tail;
+	n = (buf->head + end) & (buf->mask);
+	return (n < end) ? n : end;
+}
+
+static int inline me_circ_buf_space_to_end(me_circ_buf_t * buf)
+{
+	int end;
+	int n;
+
+//      end = buf->count - 1 - buf->head;
+//      n = (end + buf->tail) & (buf->count - 1);
+	end = buf->mask - buf->head;
+	n = (end + buf->tail) & (buf->mask);
+	return (n <= end) ? n : (end + 1);
+}
+
+#define _CBUFF_32b_t
+
+#  else	//~BOSCH
+/// @note buf->mask = buf->count-1 = ME4600_AI_CIRC_BUF_COUNT-1
+
+#   ifdef _CBUFF_32b_t
+	//32 bit
+typedef struct me_circ_buf_32b {
+	int volatile head;
+	int volatile tail;
+	unsigned int mask;	//buffor size-1 must be 2^n-1 to work
+	uint32_t *buf;
+} me_circ_buf_t;
+#   else
+	//16 bit
+typedef struct me_circ_buf_16b {
+	int volatile head;
+	int volatile tail;
+	unsigned int mask;	//buffor size-1 must be 2^n-1 to work
+	uint16_t *buf;
+} me_circ_buf_t;
+#   endif //_CBUFF_32b_t
+
+/** How many values is in buffer */
+static int inline me_circ_buf_values(me_circ_buf_t * buf)
+{
+	return ((buf->head - buf->tail) & (buf->mask));
+}
+
+/** How many space left */
+static int inline me_circ_buf_space(me_circ_buf_t * buf)
+{
+	return ((buf->tail - (buf->head + 1)) & (buf->mask));
+}
+
+/** How many values can be read from buffor in one chunck. */
+static int inline me_circ_buf_values_to_end(me_circ_buf_t * buf)
+{
+	return (buf->tail <=
+		buf->head) ? (buf->head - buf->tail) : (buf->mask - buf->tail +
+							1);
+}
+
+/** How many values can be write to buffer in one chunck. */
+static int inline me_circ_buf_space_to_end(me_circ_buf_t * buf)
+{
+	return (buf->tail <=
+		buf->head) ? (buf->mask - buf->head + 1) : (buf->tail -
+							    buf->head - 1);
+}
+
+#  endif //BOSCH
+# endif	//__KERNEL__
+#endif //_MECIRC_BUF_H_

部分文件因为文件数量过多而无法显示