[PATCH v3] staging: comedi: add NI USB-6501 support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Enable support for the National Instruments USB-6501 module.

The NI USB-6501 is a Full-Speed USB 2.0 (12 Mbit/s) device that
provides 24 digital I/O lines channels and one 32-bit counter.

This is a preliminary version:
	GPIO: works
	counter: doesn't work

Signed-off-by: Luca Ellero <luca.ellero@xxxxxxxxxxxxxxxx>
---
Changes for v2:
- rename file to ni_usb6501
- migrate comments to the standard block comment format
- fix checkpatch.pl --strict checks:
	"Please don't use multiple blank lines"
- add verbose config description to avoid checkpatch warning:	
	"please write a paragraph that describes the config symbol fully"
- correct some spelling mistakes

Changes for v3:
- fix extra space characters
- remove some useless blanks lines
- rewrite ni6501_dio_insn_bits() as suggested by Dan Carpenter (replace redundant code with for loops)

 drivers/staging/comedi/Kconfig              |   11 +
 drivers/staging/comedi/drivers/Makefile     |    1 +
 drivers/staging/comedi/drivers/ni_usb6501.c |  425 +++++++++++++++++++++++++++
 3 files changed, 437 insertions(+)
 create mode 100644 drivers/staging/comedi/drivers/ni_usb6501.c

diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
index 36f2c71..7d6cebc 100644
--- a/drivers/staging/comedi/Kconfig
+++ b/drivers/staging/comedi/Kconfig
@@ -1209,6 +1209,17 @@ config COMEDI_DT9812
 	  To compile this driver as a module, choose M here: the module will be
 	  called dt9812.
 
+config COMEDI_NI_USB6501
+	tristate "NI USB-6501 support"
+	---help---
+	  Enable support for the National Instruments USB-6501 module.
+
+	  The NI USB-6501 is a Full-Speed USB 2.0 (12 Mbit/s) device that
+	  provides 24 digital I/O lines channels and one 32-bit counter.
+
+	  To compile this driver as a module, choose M here: the module will be
+	  called ni_usb6501.
+
 config COMEDI_USBDUX
 	tristate "ITL USB-DUX-D support"
 	---help---
diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile
index 8873d48..87ac791 100644
--- a/drivers/staging/comedi/drivers/Makefile
+++ b/drivers/staging/comedi/drivers/Makefile
@@ -125,6 +125,7 @@ obj-$(CONFIG_COMEDI_QUATECH_DAQP_CS)	+= quatech_daqp_cs.o
 
 # Comedi USB drivers
 obj-$(CONFIG_COMEDI_DT9812)		+= dt9812.o
+obj-$(CONFIG_COMEDI_NI_USB6501)		+= ni_usb6501.o
 obj-$(CONFIG_COMEDI_USBDUX)		+= usbdux.o
 obj-$(CONFIG_COMEDI_USBDUXFAST)		+= usbduxfast.o
 obj-$(CONFIG_COMEDI_USBDUXSIGMA)	+= usbduxsigma.o
diff --git a/drivers/staging/comedi/drivers/ni_usb6501.c b/drivers/staging/comedi/drivers/ni_usb6501.c
new file mode 100644
index 0000000..6a4f965
--- /dev/null
+++ b/drivers/staging/comedi/drivers/ni_usb6501.c
@@ -0,0 +1,425 @@
+/*
+ * comedi/drivers/ni_usb6501.c
+ * Comedi driver for National Instruments USB-6501
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
+ * Copyright (C) 2014 Luca Ellero <luca.ellero@xxxxxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Driver: ni_usb6501
+ * Description: National Instruments USB-6501 module
+ * Devices: [National Instruments] USB-6501 (ni_usb6501)
+ * Author: Luca Ellero <luca.ellero@xxxxxxxxxxxxxxxx>
+ * Updated: 5 Aug 2014
+ * Status: works
+ *
+ * This driver works, but counter device is not implemented yet.
+ *
+ * Configuration Options:
+ * none
+ */
+
+/*
+ * NI-6501 - USB PROTOCOL DESCRIPTION
+ *
+ * Every command is composed by two USB packets:
+ *	- request (out)
+ *	- response (in)
+ *
+ * Every packet is at least 12 bytes long, here is the meaning of
+ *	every field (all values are hex):
+ *
+ *	byte 0 is always 00
+ *	byte 1 is always 01
+ *	byte 2 is always 00
+ *	byte 3 is the total packet length
+ *
+ *	byte 4 is always 00
+ *	byte 5 is is the total packet length - 4
+ *	byte 6 is always 01
+ *	byte 7 is the command
+ *
+ *	byte 8 is 02 (request) or 00 (response)
+ *	byte 9 is 00 (response) or 10 (port request) or 20 (counter request)
+ *	byte 10 is always 00
+ *	byte 11 is 00 (request) or 02 (response)
+ *
+ *
+ *	CMD: 0xE READ_PORT
+ *	REQ: 00 01 00 10 00 0C 01 0E 02 10 00 00 00 03 <PORT> 00
+ *	RES: 00 01 00 10 00 0C 01 00 00 00 00 02 00 03 <BMAP> 00
+
+ *	CMD: 0xF WRITE_PORT
+ *	REQ: 00 01 00 14 00 10 01 0F 02 10 00 00 00 03 <PORT> 00 03 <BMAP> 00 00
+ *	RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ *
+ *	CMD: 0x12 SET_PORT_DIR (0 = input, 1 = output)
+ *	REQ: 00 01 00 18 00 14 01 12 02 10 00 00
+ *	     00 05 <PORT 0> <PORT 1> <PORT 2> 00 05 00 00 00 00 00
+ *	RES: 00 01 00 0C 00 08 01 00 00 00 00 02
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "../comedidev.h"
+
+#define	NI6501_TIMEOUT	1000
+
+/* Port request packets */
+static const u8 READ_PORT_REQUEST[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x0E,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x03, 0x00, 0x00};
+
+static const u8 WRITE_PORT_REQUEST[]	= {0x00, 0x01, 0x00, 0x14,
+					   0x00, 0x10, 0x01, 0x0F,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x03, 0x00, 0x00,
+					   0x03, 0x00, 0x00, 0x00};
+
+static const u8 SET_PORT_DIR_REQUEST[]	= {0x00, 0x01, 0x00, 0x18,
+					   0x00, 0x14, 0x01, 0x12,
+					   0x02, 0x10, 0x00, 0x00,
+					   0x00, 0x05, 0x00, 0x00,
+					   0x00, 0x00, 0x05, 0x00,
+					   0x00, 0x00, 0x00, 0x00};
+
+/* Response packets */
+static const u8 GENERIC_RESPONSE[]	= {0x00, 0x01, 0x00, 0x0C,
+					   0x00, 0x08, 0x01, 0x00,
+					   0x00, 0x00, 0x00, 0x02};
+
+static const u8 READ_PORT_RESPONSE[]	= {0x00, 0x01, 0x00, 0x10,
+					   0x00, 0x0C, 0x01, 0x00,
+					   0x00, 0x00, 0x00, 0x02,
+					   0x00, 0x03, 0x00, 0x00};
+
+enum commands {
+	READ_PORT,
+	WRITE_PORT,
+	SET_PORT_DIR
+};
+
+struct ni6501_private {
+	struct usb_endpoint_descriptor *ep_rx;
+	struct usb_endpoint_descriptor *ep_tx;
+	struct semaphore sem;
+	u8 *usb_rx_buf;
+	u8 *usb_tx_buf;
+};
+
+static int ni6501_send_command(struct comedi_device *dev, int command,
+			       const u8 *port, u8 *bitmap)
+{
+	struct usb_device *usb = comedi_to_usb_dev(dev);
+	struct ni6501_private *devpriv = dev->private;
+	int request_size, response_size;
+	u8 *tx = devpriv->usb_tx_buf;
+	int ret;
+
+	if (!tx || !port)
+		return -EINVAL;
+
+	if (command != SET_PORT_DIR && !bitmap)
+		return -EINVAL;
+
+	down(&devpriv->sem);
+
+	switch (command) {
+	case READ_PORT:
+
+		request_size = sizeof(READ_PORT_REQUEST);
+		 /* 4 additional bytes for READ_PORT request */
+		response_size = sizeof(GENERIC_RESPONSE) + 4;
+
+		memcpy(tx, READ_PORT_REQUEST, request_size);
+
+		tx[14] = port[0];
+
+		break;
+
+	case WRITE_PORT:
+
+		request_size = sizeof(WRITE_PORT_REQUEST);
+		response_size = sizeof(GENERIC_RESPONSE);
+
+		memcpy(tx, WRITE_PORT_REQUEST, request_size);
+
+		tx[14] = port[0];
+		tx[17] = bitmap[0];
+
+		break;
+
+	case SET_PORT_DIR:
+
+		request_size = sizeof(SET_PORT_DIR_REQUEST);
+		response_size = sizeof(GENERIC_RESPONSE);
+
+		memcpy(tx, SET_PORT_DIR_REQUEST, request_size);
+
+		tx[14] = port[0];
+		tx[15] = port[1];
+		tx[16] = port[2];
+
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto end;
+	}
+
+	ret = usb_bulk_msg(usb,
+			   usb_sndbulkpipe(usb,
+					   devpriv->ep_tx->bEndpointAddress),
+			   devpriv->usb_tx_buf,
+			   request_size,
+			   NULL,
+			   NI6501_TIMEOUT);
+	if (ret)
+		goto end;
+
+	ret = usb_bulk_msg(usb,
+			   usb_rcvbulkpipe(usb,
+					   devpriv->ep_rx->bEndpointAddress),
+			   devpriv->usb_rx_buf,
+			   response_size,
+			   NULL,
+			   NI6501_TIMEOUT);
+	if (ret)
+		goto end;
+
+	/* Check if results are valid */
+
+	if (command == READ_PORT) {
+		bitmap[0] = devpriv->usb_rx_buf[14];
+		/* mask bitmap for comparing */
+		devpriv->usb_rx_buf[14] = 0x00;
+
+		if (memcmp(devpriv->usb_rx_buf, READ_PORT_RESPONSE,
+			   sizeof(READ_PORT_RESPONSE))) {
+			ret = -EINVAL;
+		}
+	} else if (memcmp(devpriv->usb_rx_buf, GENERIC_RESPONSE,
+			  sizeof(GENERIC_RESPONSE))) {
+		ret = -EINVAL;
+	}
+end:
+	up(&devpriv->sem);
+
+	return ret;
+}
+
+static int ni6501_dio_insn_config(struct comedi_device *dev,
+				  struct comedi_subdevice *s,
+				  struct comedi_insn *insn,
+				  unsigned int *data)
+{
+	int ret;
+	u8 port[3];
+
+	ret = comedi_dio_insn_config(dev, s, insn, data, 0);
+	if (ret)
+		return ret;
+
+	port[0] = (s->io_bits) & 0xff;
+	port[1] = (s->io_bits >> 8) & 0xff;
+	port[2] = (s->io_bits >> 16) & 0xff;
+
+	ret = ni6501_send_command(dev, SET_PORT_DIR, port, NULL);
+	if (ret)
+		return ret;
+
+	return insn->n;
+}
+
+static int ni6501_dio_insn_bits(struct comedi_device *dev,
+				struct comedi_subdevice *s,
+				struct comedi_insn *insn,
+				unsigned int *data)
+{
+	unsigned int mask;
+	int ret;
+	u8 port;
+	u8 bitmap;
+
+	mask = comedi_dio_update_state(s, data);
+
+	for (port = 0; port < 3; port++) {
+		if (mask & (0xFF << port * 8)) {
+			bitmap = (s->state >> port * 8) & 0xFF;
+			ret = ni6501_send_command(dev, WRITE_PORT,
+						  &port, &bitmap);
+			if (ret)
+				return ret;
+		}
+	}
+
+	data[1] = 0;
+
+	for (port = 0; port < 3; port++) {
+		ret = ni6501_send_command(dev, READ_PORT, &port, &bitmap);
+		if (ret)
+			return ret;
+		data[1] |= bitmap << port * 8;
+	}
+
+	return insn->n;
+}
+
+static int ni6501_alloc_usb_buffers(struct comedi_device *dev)
+{
+	struct ni6501_private *devpriv = dev->private;
+	size_t size;
+
+	size = le16_to_cpu(devpriv->ep_rx->wMaxPacketSize);
+	devpriv->usb_rx_buf = kzalloc(size, GFP_KERNEL);
+	if (!devpriv->usb_rx_buf)
+		return -ENOMEM;
+
+	size = le16_to_cpu(devpriv->ep_tx->wMaxPacketSize);
+	devpriv->usb_tx_buf = kzalloc(size, GFP_KERNEL);
+	if (!devpriv->usb_tx_buf) {
+		kfree(devpriv->usb_rx_buf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int ni6501_find_endpoints(struct comedi_device *dev)
+{
+	struct usb_interface *intf = comedi_to_usb_interface(dev);
+	struct ni6501_private *devpriv = dev->private;
+	struct usb_host_interface *iface_desc = intf->cur_altsetting;
+	struct usb_endpoint_descriptor *ep_desc;
+	int i;
+
+	if (iface_desc->desc.bNumEndpoints != 2) {
+		dev_err(dev->class_dev, "Wrong number of endpoints\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+		ep_desc = &iface_desc->endpoint[i].desc;
+
+		if (usb_endpoint_is_bulk_in(ep_desc)) {
+			if (!devpriv->ep_rx)
+				devpriv->ep_rx = ep_desc;
+			continue;
+		}
+
+		if (usb_endpoint_is_bulk_out(ep_desc)) {
+			if (!devpriv->ep_tx)
+				devpriv->ep_tx = ep_desc;
+			continue;
+		}
+	}
+
+	if (!devpriv->ep_rx || !devpriv->ep_tx)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int ni6501_auto_attach(struct comedi_device *dev,
+			      unsigned long context)
+{
+	struct usb_interface *intf = comedi_to_usb_interface(dev);
+	struct ni6501_private *devpriv;
+	struct comedi_subdevice *s;
+	int ret;
+
+	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+	if (!devpriv)
+		return -ENOMEM;
+
+	ret = ni6501_find_endpoints(dev);
+	if (ret)
+		return ret;
+
+	ret = ni6501_alloc_usb_buffers(dev);
+	if (ret)
+		return ret;
+
+	sema_init(&devpriv->sem, 1);
+	usb_set_intfdata(intf, devpriv);
+
+	ret = comedi_alloc_subdevices(dev, 1);
+	if (ret)
+		return ret;
+
+	/* Digital Input/Output subdevice */
+	s = &dev->subdevices[0];
+	s->type		= COMEDI_SUBD_DIO;
+	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
+	s->n_chan	= 24;
+	s->maxdata	= 1;
+	s->range_table	= &range_digital;
+	s->insn_bits = ni6501_dio_insn_bits;
+	s->insn_config = ni6501_dio_insn_config;
+
+	return 0;
+}
+
+static void ni6501_detach(struct comedi_device *dev)
+{
+	struct usb_interface *intf = comedi_to_usb_interface(dev);
+	struct ni6501_private *devpriv = dev->private;
+
+	if (!devpriv)
+		return;
+
+	down(&devpriv->sem);
+
+	usb_set_intfdata(intf, NULL);
+
+	kfree(devpriv->usb_rx_buf);
+	kfree(devpriv->usb_tx_buf);
+
+	up(&devpriv->sem);
+}
+
+static struct comedi_driver ni6501_driver = {
+	.module		= THIS_MODULE,
+	.driver_name	= "ni6501",
+	.auto_attach	= ni6501_auto_attach,
+	.detach		= ni6501_detach,
+};
+
+static int ni6501_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	return comedi_usb_auto_config(intf, &ni6501_driver, id->driver_info);
+}
+
+static const struct usb_device_id ni6501_usb_table[] = {
+	{ USB_DEVICE(0x3923, 0x718a) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, ni6501_usb_table);
+
+static struct usb_driver ni6501_usb_driver = {
+	.name		= "ni6501",
+	.id_table	= ni6501_usb_table,
+	.probe		= ni6501_usb_probe,
+	.disconnect	= comedi_usb_auto_unconfig,
+};
+module_comedi_usb_driver(ni6501_driver, ni6501_usb_driver);
+
+MODULE_AUTHOR("Luca Ellero");
+MODULE_DESCRIPTION("Comedi driver for National Instruments USB-6501");
+MODULE_LICENSE("GPL");
-- 
1.7.10.4

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel




[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux