[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1417038344-3801-2-git-send-email-george.mccollister@gmail.com>
Date: Wed, 26 Nov 2014 15:45:41 -0600
From: George McCollister <george.mccollister@...il.com>
To: unlisted-recipients:; (no To-header on input)
Cc: George McCollister <george.mccollister@...il.com>,
Jonathan Cameron <jic23@...nel.org>,
Hartmut Knaack <knaack.h@....de>,
Lars-Peter Clausen <lars@...afoo.de>,
Peter Meerwald <pmeerw@...erw.net>,
linux-kernel@...r.kernel.org (open list),
linux-iio@...r.kernel.org (open list:IIO SUBSYSTEM AND...)
Subject: [PATCH 2/2] iio: Add nt133 I/O board support
The NovaTech 133 I/O board is an expansion card for the NovaTech
OrionLXm with 16 digital input channels and 4 digital output channels.
Signed-off-by: George McCollister <george.mccollister@...il.com>
---
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/waveform/Kconfig | 14 ++
drivers/iio/waveform/Makefile | 5 +
drivers/iio/waveform/nt133.c | 572 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 593 insertions(+)
create mode 100644 drivers/iio/waveform/Kconfig
create mode 100644 drivers/iio/waveform/Makefile
create mode 100644 drivers/iio/waveform/nt133.c
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 345395e..c0af707 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -77,5 +77,6 @@ endif #IIO_TRIGGER
source "drivers/iio/pressure/Kconfig"
source "drivers/iio/proximity/Kconfig"
source "drivers/iio/temperature/Kconfig"
+source "drivers/iio/waveform/Kconfig"
endif # IIO
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 698afc2..599e34e 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -27,3 +27,4 @@ obj-y += pressure/
obj-y += proximity/
obj-y += temperature/
obj-y += trigger/
+obj-y += waveform/
diff --git a/drivers/iio/waveform/Kconfig b/drivers/iio/waveform/Kconfig
new file mode 100644
index 0000000..c97558d
--- /dev/null
+++ b/drivers/iio/waveform/Kconfig
@@ -0,0 +1,14 @@
+#
+# Waveform drivers
+#
+menu "Waveform output"
+
+config NT133
+ tristate "NovaTech 133 I/O board"
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ depends on USB
+ help
+ Say yes here to build support for NovaTech 133 I/O board.
+
+endmenu
diff --git a/drivers/iio/waveform/Makefile b/drivers/iio/waveform/Makefile
new file mode 100644
index 0000000..d59887c
--- /dev/null
+++ b/drivers/iio/waveform/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for industrial I/O waveform drivers
+#
+
+obj-$(CONFIG_NT133) += nt133.o
diff --git a/drivers/iio/waveform/nt133.c b/drivers/iio/waveform/nt133.c
new file mode 100644
index 0000000..8fdf172
--- /dev/null
+++ b/drivers/iio/waveform/nt133.c
@@ -0,0 +1,572 @@
+/*
+ * NovaTech 133 I/O board driver
+ *
+ * Copyright 2014 NovaTech LLC.
+ *
+ * George McCollister <george.mccollister@...il.com>
+ *
+ * Portions derivied from USB Skeleton driver - 2.2
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define NT133_DRV_NAME "nt133"
+#define NT133_VENDOR_ID 0x2aeb
+#define NT133_PRODUCT_ID 133
+
+#define NT133_USB_REQ_INPUT_STATUS 0x10
+#define NT133_USB_REQ_OUTPUT_STATUS 0x11
+#define NT133_USB_REQ_OUTPUT_CTRL 0x12
+#define NT133_USB_REQ_OUTPUT_ENABLE 0x13
+
+/* table of devices that work with this driver */
+static const struct usb_device_id nt133_table[] = {
+ { USB_DEVICE(NT133_VENDOR_ID, NT133_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, nt133_table);
+
+struct nt133_state {
+ /* the usb device for this device */
+ struct usb_device *udev;
+ /* the interface for this device */
+ struct usb_interface *interface;
+ /* in case we need to retract our submissions */
+ struct usb_anchor submitted;
+ /* the urb to read data with */
+ struct urb *int_in_urb;
+ /* the buffer to receive data */
+ __be16 int_in_data;
+ /* the address of the int in endpoint */
+ __u8 int_in_endpointAddr;
+ /* synchronize I/O with disconnect */
+ struct mutex io_mutex;
+ /* Interrupt end point poll interval */
+ u8 bInterval;
+ struct iio_trigger *trig;
+ u16 *buffer;
+};
+
+#define NT133_IN_CHAN(idx) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = idx, \
+ .scan_index = idx, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 1, \
+ .storagebits = 16, \
+ }, \
+}
+
+#define NT133_OUT_CHAN(chan, chan2) { \
+ .type = IIO_WAVEFORM, \
+ .indexed = 1, \
+ .scan_index = -1, \
+ .output = 1, \
+ .modified = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = chan, \
+ .channel2 = chan2, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 29, \
+ .storagebits = 32, \
+ }, \
+}
+
+static const struct iio_chan_spec nt133_channels[] = {
+ NT133_IN_CHAN(0),
+ NT133_IN_CHAN(1),
+ NT133_IN_CHAN(2),
+ NT133_IN_CHAN(3),
+ NT133_IN_CHAN(4),
+ NT133_IN_CHAN(5),
+ NT133_IN_CHAN(6),
+ NT133_IN_CHAN(7),
+ NT133_IN_CHAN(8),
+ NT133_IN_CHAN(9),
+ NT133_IN_CHAN(10),
+ NT133_IN_CHAN(11),
+ NT133_IN_CHAN(12),
+ NT133_IN_CHAN(13),
+ NT133_IN_CHAN(14),
+ NT133_IN_CHAN(15),
+ IIO_CHAN_SOFT_TIMESTAMP(16),
+ NT133_OUT_CHAN(0, IIO_MOD_HIGHTIME),
+ NT133_OUT_CHAN(0, IIO_MOD_LOWTIME),
+ NT133_OUT_CHAN(1, IIO_MOD_HIGHTIME),
+ NT133_OUT_CHAN(1, IIO_MOD_LOWTIME),
+ NT133_OUT_CHAN(2, IIO_MOD_HIGHTIME),
+ NT133_OUT_CHAN(2, IIO_MOD_LOWTIME),
+ NT133_OUT_CHAN(3, IIO_MOD_HIGHTIME),
+ NT133_OUT_CHAN(3, IIO_MOD_LOWTIME),
+};
+
+
+static void nt133_read_int_callback(struct urb *urb);
+
+static int nt133_do_read_io(struct nt133_state *st)
+{
+ int rv;
+
+ mutex_lock(&st->io_mutex);
+ if (!st->interface) {
+ mutex_unlock(&st->io_mutex);
+ rv = -ENODEV;
+ goto error;
+ }
+
+ usb_fill_int_urb(st->int_in_urb,
+ st->udev,
+ usb_rcvintpipe(st->udev, st->int_in_endpointAddr),
+ &st->int_in_data,
+ sizeof(st->int_in_data),
+ nt133_read_int_callback,
+ st,
+ st->bInterval);
+ usb_anchor_urb(st->int_in_urb, &st->submitted);
+
+ rv = usb_submit_urb(st->int_in_urb, GFP_KERNEL);
+ mutex_unlock(&st->io_mutex);
+ if (rv < 0) {
+ usb_unanchor_urb(st->int_in_urb);
+ dev_err(&st->interface->dev,
+ "%s - failed submitting read urb, error %d\n",
+ __func__, rv);
+ }
+error:
+ return rv;
+}
+
+static void nt133_read_int_callback(struct urb *urb)
+{
+ struct nt133_state *st;
+
+ st = urb->context;
+
+ dev_dbg(&st->interface->dev,
+ "%s - urb status = %d, actual length = %d\n",
+ __func__, urb->status, urb->actual_length);
+
+ if (urb->status) {
+ if (!(urb->status == -ENOENT ||
+ urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ dev_err(&st->interface->dev,
+ "%s - nonzero write int status received: %d\n",
+ __func__, urb->status);
+ nt133_do_read_io(st);
+ } else if (urb->actual_length == sizeof(st->int_in_data)) {
+ iio_trigger_poll(st->trig);
+ } else {
+ dev_err(&st->interface->dev,
+ "%s - unexpected size of %d\n",
+ __func__, urb->actual_length);
+ nt133_do_read_io(st);
+ }
+}
+
+static int nt133_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long m)
+{
+ struct nt133_state *st = iio_priv(indio_dev);
+ int rv = -EINVAL;
+ u16 inbits;
+
+ switch (m) {
+ case IIO_CHAN_INFO_RAW:
+ rv = usb_control_msg(st->udev,
+ usb_rcvctrlpipe(st->udev, 0),
+ NT133_USB_REQ_INPUT_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR,
+ 0,
+ 0x0,
+ &inbits,
+ sizeof(inbits),
+ USB_CTRL_SET_TIMEOUT);
+ if (rv < 0) {
+ dev_err(&st->interface->dev,
+ "%s - failed to get input status, error %d\n",
+ __func__, rv);
+ } else {
+ *val = (int) (be16_to_cpu(inbits) >> chan->channel)
+ & 0x1;
+ rv = IIO_VAL_INT;
+ }
+ break;
+ case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_VOLTAGE) {
+ /* A raw value of 0 indicates 0VDC */
+ /* A raw value of 1 indicates 90VDC */
+ *val = 90000000UL;
+ rv = IIO_VAL_INT;
+ } else if (chan->type == IIO_WAVEFORM) {
+ /* Scale raw time (milliseconds) to seconds */
+ *val = 0;
+ *val2 = 1000;
+ rv = IIO_VAL_INT_PLUS_MICRO;
+ }
+ break;
+ }
+
+ return rv;
+}
+
+static int nt133_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val,
+ int val2,
+ long m)
+{
+ struct nt133_state *st = iio_priv(indio_dev);
+ const int max_val = (1 << chan->scan_type.realbits);
+ int rv;
+ u16 direction;
+ __be32 duration;
+
+ if (chan->channel2 == IIO_MOD_HIGHTIME)
+ direction = 0x1;
+ else if (chan->channel2 == IIO_MOD_LOWTIME)
+ direction = 0x0;
+ else
+ return -EINVAL;
+
+ if (val >= max_val || val < 0)
+ return -EINVAL;
+
+ /* Check if output is already in progress */
+ rv = usb_control_msg(st->udev,
+ usb_rcvctrlpipe(st->udev, 0),
+ NT133_USB_REQ_OUTPUT_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR,
+ 0x0,
+ (u16)chan->channel,
+ &duration,
+ sizeof(duration),
+ USB_CTRL_SET_TIMEOUT);
+ if (rv < 0) {
+ dev_err(&st->interface->dev,
+ "%s - failed to get output status, error %d\n",
+ __func__, rv);
+ return rv;
+ }
+
+ if (be32_to_cpu(duration))
+ return -EBUSY;
+
+ duration = cpu_to_be32((u32)val);
+
+ rv = usb_control_msg(st->udev,
+ usb_sndctrlpipe(st->udev, 0),
+ NT133_USB_REQ_OUTPUT_CTRL,
+ USB_DIR_OUT | USB_TYPE_VENDOR,
+ direction,
+ (u16)chan->channel,
+ &duration,
+ sizeof(duration),
+ USB_CTRL_SET_TIMEOUT);
+ if (rv < 0)
+ dev_err(&st->interface->dev,
+ "%s - failed to control output, error %d\n",
+ __func__, rv);
+ return rv;
+}
+
+static int nt133_enable_outputs(struct nt133_state *st)
+{
+ int rv;
+
+ rv = usb_control_msg(st->udev,
+ usb_sndctrlpipe(st->udev, 0),
+ NT133_USB_REQ_OUTPUT_ENABLE,
+ USB_DIR_OUT | USB_TYPE_VENDOR,
+ 0x1, /*0=disabled, 1=enabled*/
+ 0x0,
+ NULL,
+ 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (rv < 0)
+ dev_err(&st->interface->dev,
+ "%s - failed to enable outputs, error %d\n",
+ __func__, rv);
+ return rv;
+}
+
+static irqreturn_t nt133_trigger_handler(int irq, void *private)
+{
+ struct iio_poll_func *pf = private;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct nt133_state *st = iio_priv(indio_dev);
+ int i;
+ int j = 0;
+
+ dev_dbg(&st->interface->dev,
+ "%s - active_scan_mask=%d, masklength=%d\n",
+ __func__, (int)indio_dev->active_scan_mask[0],
+ indio_dev->masklength);
+
+ for_each_set_bit(i, indio_dev->active_scan_mask,
+ indio_dev->masklength) {
+ st->buffer[j++] = (u16)
+ (be16_to_cpu(st->int_in_data) >> i) & 0x1;
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, st->buffer,
+ pf->timestamp);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ nt133_do_read_io(st);
+
+ return IRQ_HANDLED;
+}
+
+static int nt133_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct nt133_state *st = iio_priv(indio_dev);
+
+ dev_dbg(&st->interface->dev,
+ "%s - scan_bytes=%d\n",
+ __func__, indio_dev->scan_bytes);
+
+ kfree(st->buffer);
+ st->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);
+ if (st->buffer == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+static const struct iio_trigger_ops nt133_trigger_ops = {
+ .owner = THIS_MODULE,
+};
+
+static const struct iio_info nt133_info = {
+ .driver_module = THIS_MODULE,
+ .read_raw = nt133_read_raw,
+ .write_raw = nt133_write_raw,
+ .update_scan_mode = nt133_update_scan_mode,
+};
+
+static int nt133_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct iio_dev *indio_dev;
+ struct iio_trigger *trig;
+ struct nt133_state *st;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int i;
+ int retval = -ENOMEM;
+
+ /* allocate memory for our device state and initialize it */
+ indio_dev = devm_iio_device_alloc(&interface->dev, sizeof(*st));
+ if (!indio_dev) {
+ dev_err(&interface->dev, "Out of memory\n");
+ goto error_ret;
+ }
+
+ st = iio_priv(indio_dev);
+
+ mutex_init(&st->io_mutex);
+ init_usb_anchor(&st->submitted);
+
+ st->udev = usb_get_dev(interface_to_usbdev(interface));
+ st->interface = interface;
+
+ /* set up the endpoint information */
+ /* use only the first int-in endpoint */
+ iface_desc = interface->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+
+ if (!st->int_in_endpointAddr &&
+ usb_endpoint_is_int_in(endpoint)) {
+ /* we found a int in endpoint */
+ st->int_in_endpointAddr = endpoint->bEndpointAddress;
+ st->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!st->int_in_urb) {
+ dev_err(&interface->dev,
+ "Couldn't allocate int_in_urb.\n");
+ goto error_put_dev;
+ }
+ st->bInterval = endpoint->bInterval;
+ break;
+ }
+ }
+
+ if (!(st->int_in_endpointAddr)) {
+ dev_err(&interface->dev,
+ "Couldn't find int-in endpoint.\n");
+ goto error_put_dev;
+ }
+
+ indio_dev->dev.parent = &interface->dev;
+ indio_dev->name = NT133_DRV_NAME;
+ indio_dev->channels = nt133_channels;
+ indio_dev->num_channels = ARRAY_SIZE(nt133_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &nt133_info;
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, indio_dev);
+
+ trig = devm_iio_trigger_alloc(&interface->dev, "%s-dev%d",
+ indio_dev->name, indio_dev->id);
+ if (!trig)
+ goto error_free_urb;
+
+ st->trig = trig;
+ trig->dev.parent = indio_dev->dev.parent;
+ iio_trigger_set_drvdata(trig, indio_dev);
+ trig->ops = &nt133_trigger_ops;
+
+ retval = iio_trigger_register(trig);
+ if (retval) {
+ dev_err(&interface->dev, "Failed to register trigger.\n");
+ goto error_free_urb;
+ }
+
+ retval = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
+ &nt133_trigger_handler, NULL);
+ if (retval) {
+ dev_err(&interface->dev, "Failed to setup triggered buffer.\n");
+ goto error_unregister_trigger;
+ }
+
+ retval = iio_device_register(indio_dev);
+ if (retval) {
+ dev_err(&interface->dev, "Failed to regsiter device.\n");
+ goto error_triggered_buffer_cleanup;
+ }
+
+ retval = nt133_enable_outputs(st);
+ if (retval)
+ goto error_device_unregister;
+
+ retval = nt133_do_read_io(st);
+ if (retval)
+ goto error_device_unregister;
+
+ return 0;
+
+error_device_unregister:
+ iio_device_unregister(indio_dev);
+error_triggered_buffer_cleanup:
+ iio_triggered_buffer_cleanup(indio_dev);
+error_unregister_trigger:
+ iio_trigger_unregister(st->trig);
+error_free_urb:
+ usb_free_urb(st->int_in_urb);
+error_put_dev:
+ usb_put_dev(st->udev);
+error_ret:
+ usb_set_intfdata(interface, NULL);
+ return retval;
+}
+
+static void nt133_disconnect(struct usb_interface *interface)
+{
+ struct iio_dev *indio_dev = usb_get_intfdata(interface);
+ struct nt133_state *st = iio_priv(indio_dev);
+
+ usb_set_intfdata(interface, NULL);
+
+ /* prevent more I/O from starting */
+ mutex_lock(&st->io_mutex);
+ st->interface = NULL;
+ mutex_unlock(&st->io_mutex);
+
+ usb_kill_anchored_urbs(&st->submitted);
+
+ iio_device_unregister(indio_dev);
+
+ iio_triggered_buffer_cleanup(indio_dev);
+
+ iio_trigger_unregister(st->trig);
+
+ usb_free_urb(st->int_in_urb);
+ usb_put_dev(st->udev);
+
+ dev_info(&interface->dev, "nt133 now disconnected");
+}
+
+static void nt133_draw_down(struct nt133_state *st)
+{
+ int time;
+
+ time = usb_wait_anchor_empty_timeout(&st->submitted, 1000);
+ if (!time)
+ usb_kill_anchored_urbs(&st->submitted);
+ usb_kill_urb(st->int_in_urb);
+}
+
+static int nt133_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct nt133_state *st = usb_get_intfdata(intf);
+
+ if (!st)
+ return 0;
+ nt133_draw_down(st);
+ return 0;
+}
+
+static int nt133_resume(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static int nt133_pre_reset(struct usb_interface *intf)
+{
+ struct nt133_state *st = usb_get_intfdata(intf);
+
+ mutex_lock(&st->io_mutex);
+ nt133_draw_down(st);
+
+ return 0;
+}
+
+static int nt133_post_reset(struct usb_interface *intf)
+{
+ struct nt133_state *st = usb_get_intfdata(intf);
+
+ /* we are sure no URBs are active - no locking needed */
+ mutex_unlock(&st->io_mutex);
+
+ return 0;
+}
+
+static struct usb_driver nt133_driver = {
+ .name = NT133_DRV_NAME,
+ .probe = nt133_probe,
+ .disconnect = nt133_disconnect,
+ .suspend = nt133_suspend,
+ .resume = nt133_resume,
+ .pre_reset = nt133_pre_reset,
+ .post_reset = nt133_post_reset,
+ .id_table = nt133_table,
+};
+
+module_usb_driver(nt133_driver);
+
+MODULE_LICENSE("GPL");
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists