Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753365AbaKZVtI (ORCPT ); Wed, 26 Nov 2014 16:49:08 -0500 Received: from mail-oi0-f46.google.com ([209.85.218.46]:36463 "EHLO mail-oi0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753212AbaKZVtC (ORCPT ); Wed, 26 Nov 2014 16:49:02 -0500 From: George McCollister Cc: George McCollister , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald , linux-kernel@vger.kernel.org (open list), linux-iio@vger.kernel.org (open list:IIO SUBSYSTEM AND...) Subject: [PATCH 2/2] iio: Add nt133 I/O board support Date: Wed, 26 Nov 2014 15:45:41 -0600 Message-Id: <1417038344-3801-2-git-send-email-george.mccollister@gmail.com> X-Mailer: git-send-email 2.1.0 In-Reply-To: <1417038344-3801-1-git-send-email-george.mccollister@gmail.com> References: <1417038344-3801-1-git-send-email-george.mccollister@gmail.com> To: unlisted-recipients:; (no To-header on input) Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- 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 + * + * Portions derivied from USB Skeleton driver - 2.2 + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/