From: "Devin J. Pohly" <djpohly@gmail.com>
To: linux-input@vger.kernel.org
Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>,
"Devin J. Pohly" <djpohly@gmail.com>
Subject: [PATCH] Input: add support for PIUIO input/output board
Date: Tue, 23 Jan 2018 13:20:29 -0600 [thread overview]
Message-ID: <20180123192029.9468-2-djpohly@gmail.com> (raw)
In-Reply-To: <20180123192029.9468-1-djpohly@gmail.com>
The PIUIO is a digital input/output board most often found in arcade
dance cabinets. It uses a polling-based USB protocol to get/set the
state of inputs and outputs, with the potential for up to 48 of each
(though actual boards have fewer physical connections).
This commit provides a driver which exposes the inputs as joystick
buttons and the outputs as LEDs.
The driver also supports a smaller form of the board with 8 inputs and
outputs which uses the same protocol.
Signed-off-by: Devin J. Pohly <djpohly@gmail.com>
---
MAINTAINERS | 6 +
drivers/input/joystick/Kconfig | 11 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/piuio.c | 672 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 690 insertions(+)
create mode 100644 drivers/input/joystick/piuio.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2f4e462aa4a2..a39675bff62a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10691,6 +10691,12 @@ F: arch/mips/include/asm/mach-pistachio/
F: arch/mips/boot/dts/img/pistachio*
F: arch/mips/configs/pistachio*_defconfig
+PIUIO DRIVER
+M: Devin J. Pohly <djpohly@gmail.com>
+W: https://github.com/djpohly/piuio
+S: Maintained
+F: drivers/input/joystick/piuio.c
+
PKTCDVD DRIVER
S: Orphan
M: linux-block@vger.kernel.org
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index f3c2f6ea8b44..5d156e760db8 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -351,4 +351,15 @@ config JOYSTICK_PSXPAD_SPI_FF
To drive rumble motor a dedicated power supply is required.
+config JOYSTICK_PIUIO
+ tristate "PIUIO input/output interface"
+ depends on USB_ARCH_HAS_HCD
+ select USB
+ help
+ Say Y here if you want to use the PIUIO interface board for
+ digital inputs/outputs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called piuio.
+
endif
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 67651efda2e1..33c4e54fb516 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_JOYSTICK_INTERACT) += interact.o
obj-$(CONFIG_JOYSTICK_JOYDUMP) += joydump.o
obj-$(CONFIG_JOYSTICK_MAGELLAN) += magellan.o
obj-$(CONFIG_JOYSTICK_MAPLE) += maplecontrol.o
+obj-$(CONFIG_JOYSTICK_PIUIO) += piuio.o
obj-$(CONFIG_JOYSTICK_PSXPAD_SPI) += psxpad-spi.o
obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
diff --git a/drivers/input/joystick/piuio.c b/drivers/input/joystick/piuio.c
new file mode 100644
index 000000000000..e1d9b34692e2
--- /dev/null
+++ b/drivers/input/joystick/piuio.c
@@ -0,0 +1,672 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Andamiro PIU IO input module, for use with the PIUIO input/output boards
+// commonly found in arcade dance cabinets.
+//
+// Copyright (C) 2012-2018 Devin J. Pohly (djpohly@gmail.com)
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/sysfs.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/leds.h>
+#include <linux/wait.h>
+#include <linux/jiffies.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+
+
+/*
+ * Device and protocol definitions
+ */
+#define USB_VENDOR_ID_ANCHOR 0x0547
+#define USB_PRODUCT_ID_PYTHON2 0x1002
+#define USB_VENDOR_ID_BTNBOARD 0x0d2f
+#define USB_PRODUCT_ID_BTNBOARD 0x1010
+
+/* USB message used to communicate with the device */
+#define PIUIO_MSG_REQ 0xae
+#define PIUIO_MSG_VAL 0
+#define PIUIO_MSG_IDX 0
+
+#define PIUIO_MSG_SZ 8
+#define PIUIO_MSG_LONGS (PIUIO_MSG_SZ / sizeof(unsigned long))
+
+/* Input keycode ranges */
+#define PIUIO_BTN_REG BTN_JOYSTICK
+#define PIUIO_NUM_REG (BTN_GAMEPAD - BTN_JOYSTICK)
+#define PIUIO_BTN_EXTRA BTN_TRIGGER_HAPPY
+#define PIUIO_NUM_EXTRA (KEY_MAX - BTN_TRIGGER_HAPPY)
+#define PIUIO_NUM_BTNS (PIUIO_NUM_REG + PIUIO_NUM_EXTRA)
+
+
+#ifdef CONFIG_LEDS_CLASS
+/**
+ * struct piuio_led - auxiliary struct for led devices
+ * @piu: Pointer back to the enclosing structure
+ * @dev: Actual led device
+ */
+struct piuio_led {
+ struct piuio *piu;
+ struct led_classdev dev;
+};
+
+static const char *const led_names[] = {
+ "piuio::output0",
+ "piuio::output1",
+ "piuio::output2",
+ "piuio::output3",
+ "piuio::output4",
+ "piuio::output5",
+ "piuio::output6",
+ "piuio::output7",
+ "piuio::output8",
+ "piuio::output9",
+ "piuio::output10",
+ "piuio::output11",
+ "piuio::output12",
+ "piuio::output13",
+ "piuio::output14",
+ "piuio::output15",
+ "piuio::output16",
+ "piuio::output17",
+ "piuio::output18",
+ "piuio::output19",
+ "piuio::output20",
+ "piuio::output21",
+ "piuio::output22",
+ "piuio::output23",
+ "piuio::output24",
+ "piuio::output25",
+ "piuio::output26",
+ "piuio::output27",
+ "piuio::output28",
+ "piuio::output29",
+ "piuio::output30",
+ "piuio::output31",
+ "piuio::output32",
+ "piuio::output33",
+ "piuio::output34",
+ "piuio::output35",
+ "piuio::output36",
+ "piuio::output37",
+ "piuio::output38",
+ "piuio::output39",
+ "piuio::output40",
+ "piuio::output41",
+ "piuio::output42",
+ "piuio::output43",
+ "piuio::output44",
+ "piuio::output45",
+ "piuio::output46",
+ "piuio::output47",
+};
+
+static const char *const bbled_names[] = {
+ "piuio::bboutput0",
+ "piuio::bboutput1",
+ "piuio::bboutput2",
+ "piuio::bboutput3",
+ "piuio::bboutput4",
+ "piuio::bboutput5",
+ "piuio::bboutput6",
+ "piuio::bboutput7",
+};
+#endif
+
+/**
+ * struct piuio_devtype - parameters for different types of PIUIO devices
+ * @led_names: Array of LED names, of length @outputs, to use in sysfs
+ * @inputs: Number of input pins
+ * @outputs: Number of output pins
+ * @mplex: Number of sets of inputs
+ * @mplex_bits: Number of output bits reserved for multiplexing
+ */
+struct piuio_devtype {
+#ifdef CONFIG_LEDS_CLASS
+ const char *const *led_names;
+#endif
+ int inputs;
+ int outputs;
+ int mplex;
+ int mplex_bits;
+};
+
+/**
+ * struct piuio - state of each attached PIUIO
+ * @type: Type of PIUIO device (currently either full or buttonboard)
+ * @idev: Input device associated with this PIUIO
+ * @phys: Physical path of the device. @idev's phys field points to this
+ * buffer
+ * @udev: USB device associated with this PIUIO
+ * @in: URB for requesting the current state of one set of inputs
+ * @out: URB for sending data to outputs and multiplexer
+ * @cr_in: Setup packet for @in URB
+ * @cr_out: Setup packet for @out URB
+ * @old_inputs: Previous state of input pins from the @in URB for each of the
+ * input sets. These are used to determine when a press or release
+ * has happened for a group of correlated inputs.
+ * @inputs: Buffer for the @in URB
+ * @outputs: Buffer for the @out URB
+ * @new_outputs:
+ * Staging for the @outputs buffer
+ * @set: Current set of inputs to read (0 .. @type->mplex - 1)
+ */
+struct piuio {
+ struct piuio_devtype *type;
+
+ struct input_dev *idev;
+ char phys[64];
+
+ struct usb_device *udev;
+ struct urb *in, *out;
+ struct usb_ctrlrequest cr_in, cr_out;
+ wait_queue_head_t shutdown_wait;
+
+ unsigned long (*old_inputs)[PIUIO_MSG_LONGS];
+ unsigned long inputs[PIUIO_MSG_LONGS];
+ unsigned char outputs[PIUIO_MSG_SZ];
+ unsigned char new_outputs[PIUIO_MSG_SZ];
+
+#ifdef CONFIG_LEDS_CLASS
+ struct piuio_led *led;
+#endif
+
+ int set;
+};
+
+/* Full device parameters */
+static struct piuio_devtype piuio_dev_full = {
+#ifdef CONFIG_LEDS_CLASS
+ .led_names = led_names,
+#endif
+ .inputs = (PIUIO_NUM_BTNS < 48) ? PIUIO_NUM_BTNS : 48,
+ .outputs = 48,
+ .mplex = 4,
+ .mplex_bits = 2,
+};
+
+/* Button board device parameters */
+static struct piuio_devtype piuio_dev_bb = {
+#ifdef CONFIG_LEDS_CLASS
+ .led_names = bbled_names,
+#endif
+ .inputs = (PIUIO_NUM_BTNS < 8) ? PIUIO_NUM_BTNS : 8,
+ .outputs = 8,
+ .mplex = 1,
+ .mplex_bits = 0,
+};
+
+
+/*
+ * Auxiliary functions for reporting input events
+ */
+static int keycode(unsigned int pin)
+{
+ /* Use joystick buttons first, then the extra "trigger happy" range. */
+ if (pin < PIUIO_NUM_REG)
+ return PIUIO_BTN_REG + pin;
+ pin -= PIUIO_NUM_REG;
+ return PIUIO_BTN_EXTRA + pin;
+}
+
+
+/*
+ * URB completion handlers
+ */
+static void piuio_in_completed(struct urb *urb)
+{
+ struct piuio *piu = urb->context;
+ unsigned long changed[PIUIO_MSG_LONGS];
+ unsigned long b;
+ int i, s;
+ int cur_set;
+ int ret = urb->status;
+
+ if (ret) {
+ dev_warn(&piu->udev->dev,
+ "piuio callback(in): error %d\n", ret);
+ goto resubmit;
+ }
+
+ /* Get index of the previous input set (always 0 if no multiplexer) */
+ cur_set = (piu->set + piu->type->mplex - 1) % piu->type->mplex;
+
+ /* Note what has changed in this input set, then store the inputs for
+ * next time
+ */
+ for (i = 0; i < PIUIO_MSG_LONGS; i++) {
+ changed[i] = piu->inputs[i] ^ piu->old_inputs[cur_set][i];
+ piu->old_inputs[cur_set][i] = piu->inputs[i];
+ }
+
+ /* If we are using a multiplexer, changes only count when none of the
+ * corresponding inputs in other sets are pressed. Since "pressed"
+ * reads as 0, we can use & to knock those bits out of the changes.
+ */
+ for (s = 0; s < piu->type->mplex; s++) {
+ if (s == cur_set)
+ continue;
+ for (i = 0; i < PIUIO_MSG_LONGS; i++)
+ changed[i] &= piu->old_inputs[s][i];
+ }
+
+ /* For each input which has changed state, report whether it was pressed
+ * or released based on the current value.
+ */
+ for_each_set_bit(b, changed, piu->type->inputs) {
+ input_event(piu->idev, EV_MSC, MSC_SCAN, b + 1);
+ input_report_key(piu->idev, keycode(b),
+ !test_bit(b, piu->inputs));
+ }
+
+ /* Done reporting input events */
+ input_sync(piu->idev);
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret == -EPERM)
+ dev_info(&piu->udev->dev, "piuio resubmit(in): shutdown\n");
+ else if (ret)
+ dev_err(&piu->udev->dev, "piuio resubmit(in): error %d\n", ret);
+
+ /* Let any waiting threads know we're done here */
+ wake_up(&piu->shutdown_wait);
+}
+
+static void piuio_out_completed(struct urb *urb)
+{
+ struct piuio *piu = urb->context;
+ int ret = urb->status;
+
+ if (ret) {
+ dev_warn(&piu->udev->dev,
+ "piuio callback(out): error %d\n", ret);
+ goto resubmit;
+ }
+
+ /* Copy in the new outputs */
+ memcpy(piu->outputs, piu->new_outputs, PIUIO_MSG_SZ);
+
+ /* If we have a multiplexer, switch to the next input set in rotation
+ * and set the appropriate output bits
+ */
+ piu->set = (piu->set + 1) % piu->type->mplex;
+
+ /* Set multiplexer bits */
+ piu->outputs[0] &= ~((1 << piu->type->mplex_bits) - 1);
+ piu->outputs[0] |= piu->set;
+ piu->outputs[2] &= ~((1 << piu->type->mplex_bits) - 1);
+ piu->outputs[2] |= piu->set;
+
+resubmit:
+ ret = usb_submit_urb(piu->out, GFP_ATOMIC);
+ if (ret == -EPERM)
+ dev_info(&piu->udev->dev, "piuio resubmit(out): shutdown\n");
+ else if (ret)
+ dev_err(&piu->udev->dev,
+ "piuio resubmit(out): error %d\n", ret);
+
+ /* Let any waiting threads know we're done here */
+ wake_up(&piu->shutdown_wait);
+}
+
+
+/*
+ * Input device events
+ */
+static int piuio_open(struct input_dev *idev)
+{
+ struct piuio *piu = input_get_drvdata(idev);
+ int ret;
+
+ /* Kick off the polling */
+ ret = usb_submit_urb(piu->out, GFP_KERNEL);
+ if (ret) {
+ dev_err(&piu->udev->dev, "piuio submit(out): error %d\n", ret);
+ return -EIO;
+ }
+
+ ret = usb_submit_urb(piu->in, GFP_KERNEL);
+ if (ret) {
+ dev_err(&piu->udev->dev, "piuio submit(in): error %d\n", ret);
+ usb_kill_urb(piu->out);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void piuio_close(struct input_dev *idev)
+{
+ struct piuio *piu = input_get_drvdata(idev);
+ long remaining;
+
+ /* Stop polling, but wait for the last requests to complete */
+ usb_block_urb(piu->in);
+ usb_block_urb(piu->out);
+ remaining = wait_event_timeout(piu->shutdown_wait,
+ atomic_read(&piu->in->use_count) == 0 &&
+ atomic_read(&piu->out->use_count) == 0,
+ msecs_to_jiffies(5));
+ usb_unblock_urb(piu->in);
+ usb_unblock_urb(piu->out);
+
+ if (!remaining) {
+ // Timed out
+ dev_warn(&piu->udev->dev, "piuio close: urb timeout\n");
+ usb_kill_urb(piu->in);
+ usb_kill_urb(piu->out);
+ }
+
+ /* XXX Reset the outputs? */
+}
+
+
+/*
+ * Structure initialization and destruction
+ */
+static void piuio_input_init(struct piuio *piu, struct device *parent)
+{
+ struct input_dev *idev = piu->idev;
+ int i;
+
+ /* Fill in basic fields */
+ idev->name = "PIUIO input";
+ idev->phys = piu->phys;
+ usb_to_input_id(piu->udev, &idev->id);
+ idev->dev.parent = parent;
+
+ /* HACK: Buttons are sufficient to trigger a /dev/input/js* device, but
+ * for systemd (and consequently udev and Xorg) to consider us a
+ * joystick, we have to have a set of XY absolute axes.
+ */
+ set_bit(EV_KEY, idev->evbit);
+ set_bit(EV_ABS, idev->evbit);
+
+ /* Configure buttons */
+ for (i = 0; i < piu->type->inputs; i++)
+ set_bit(keycode(i), idev->keybit);
+ clear_bit(0, idev->keybit);
+
+ /* Configure fake axes */
+ set_bit(ABS_X, idev->absbit);
+ set_bit(ABS_Y, idev->absbit);
+ input_set_abs_params(idev, ABS_X, 0, 0, 0, 0);
+ input_set_abs_params(idev, ABS_Y, 0, 0, 0, 0);
+
+ /* Set device callbacks */
+ idev->open = piuio_open;
+ idev->close = piuio_close;
+
+ /* Link input device back to PIUIO */
+ input_set_drvdata(idev, piu);
+}
+
+
+#ifdef CONFIG_LEDS_CLASS
+/*
+ * Led device event
+ */
+static void piuio_led_set(struct led_classdev *dev, enum led_brightness b)
+{
+ struct piuio_led *led = container_of(dev, struct piuio_led, dev);
+ struct piuio *piu = led->piu;
+ int n;
+
+ n = led - piu->led;
+ if (n > piu->type->outputs) {
+ dev_err(&piu->udev->dev, "piuio led: bad number %d\n", n);
+ return;
+ }
+
+ /* Meh, forget atomicity, these aren't super-important */
+ if (b)
+ __set_bit(n, (unsigned long *) piu->new_outputs);
+ else
+ __clear_bit(n, (unsigned long *) piu->new_outputs);
+}
+
+int
+piu_led_register(struct piuio_led *led)
+{
+ const struct attribute_group **ag;
+ struct attribute **attr;
+ int ret;
+
+ /* Register led device */
+ ret = led_classdev_register(&led->piu->udev->dev, &led->dev);
+ if (ret)
+ return ret;
+
+ /* Relax permissions on led attributes */
+ for (ag = led->dev.dev->class->dev_groups; *ag; ag++) {
+ for (attr = (*ag)->attrs; *attr; attr++) {
+ ret = sysfs_chmod_file(&led->dev.dev->kobj, *attr,
+ 0666);
+ if (ret) {
+ led_classdev_unregister(&led->dev);
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static int piuio_leds_init(struct piuio *piu)
+{
+ int i;
+ int ret;
+
+ piu->led = kcalloc(piu->type->outputs, sizeof(*piu->led), GFP_KERNEL);
+ if (!piu->led)
+ return -ENOMEM;
+
+ for (i = 0; i < piu->type->outputs; i++) {
+ /* Initialize led device and point back to piuio struct */
+ piu->led[i].dev.name = piu->type->led_names[i];
+ piu->led[i].dev.brightness_set = piuio_led_set;
+ piu->led[i].piu = piu;
+
+ ret = piu_led_register(&piu->led[i]);
+ if (ret)
+ goto out_unregister;
+ }
+
+ return 0;
+
+out_unregister:
+ for (--i; i >= 0; i--)
+ led_classdev_unregister(&piu->led[i].dev);
+ kfree(piu->led);
+ return ret;
+}
+
+static void piuio_leds_destroy(struct piuio *piu)
+{
+ int i;
+
+ for (i = 0; i < piu->type->outputs; i++)
+ led_classdev_unregister(&piu->led[i].dev);
+ kfree(piu->led);
+}
+#else
+static int piuio_leds_init(struct piuio *piu) { return 0; }
+static void piuio_leds_destroy(struct piuio *piu) {}
+#endif
+
+static int piuio_init(struct piuio *piu, struct input_dev *idev,
+ struct usb_device *udev)
+{
+ /* Note: if this function returns an error, piuio_destroy will still be
+ * called, so we don't need to clean up here
+ */
+
+ /* Allocate USB request blocks */
+ piu->in = usb_alloc_urb(0, GFP_KERNEL);
+ piu->out = usb_alloc_urb(0, GFP_KERNEL);
+ if (!piu->in || !piu->out)
+ return -ENOMEM;
+
+ /* Create dynamically allocated arrays */
+ piu->old_inputs = kcalloc(piu->type->mplex, sizeof(*piu->old_inputs),
+ GFP_KERNEL);
+ if (!piu->old_inputs)
+ return -ENOMEM;
+
+ init_waitqueue_head(&piu->shutdown_wait);
+
+ piu->idev = idev;
+ piu->udev = udev;
+ usb_make_path(udev, piu->phys, sizeof(piu->phys));
+ strlcat(piu->phys, "/input0", sizeof(piu->phys));
+
+ /* Prepare URB for multiplexer and outputs */
+ piu->cr_out.bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE;
+ piu->cr_out.bRequest = PIUIO_MSG_REQ;
+ piu->cr_out.wValue = cpu_to_le16(PIUIO_MSG_VAL);
+ piu->cr_out.wIndex = cpu_to_le16(PIUIO_MSG_IDX);
+ piu->cr_out.wLength = cpu_to_le16(PIUIO_MSG_SZ);
+ usb_fill_control_urb(piu->out, udev, usb_sndctrlpipe(udev, 0),
+ (void *) &piu->cr_out, piu->outputs, PIUIO_MSG_SZ,
+ piuio_out_completed, piu);
+
+ /* Prepare URB for inputs */
+ piu->cr_in.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE;
+ piu->cr_in.bRequest = PIUIO_MSG_REQ;
+ piu->cr_in.wValue = cpu_to_le16(PIUIO_MSG_VAL);
+ piu->cr_in.wIndex = cpu_to_le16(PIUIO_MSG_IDX);
+ piu->cr_in.wLength = cpu_to_le16(PIUIO_MSG_SZ);
+ usb_fill_control_urb(piu->in, udev, usb_rcvctrlpipe(udev, 0),
+ (void *) &piu->cr_in, piu->inputs, PIUIO_MSG_SZ,
+ piuio_in_completed, piu);
+
+ return 0;
+}
+
+static void piuio_destroy(struct piuio *piu)
+{
+ /* These handle NULL gracefully, so we can call this to clean up if init
+ * fails
+ */
+ kfree(piu->old_inputs);
+ usb_free_urb(piu->out);
+ usb_free_urb(piu->in);
+}
+
+
+/*
+ * USB connect and disconnect events
+ */
+static int piuio_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct piuio *piu;
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct input_dev *idev;
+ int ret = -ENOMEM;
+
+ /* Allocate PIUIO state and determine device type */
+ piu = kzalloc(sizeof(struct piuio), GFP_KERNEL);
+ if (!piu)
+ return ret;
+
+ if (id->idVendor == USB_VENDOR_ID_BTNBOARD &&
+ id->idProduct == USB_PRODUCT_ID_BTNBOARD) {
+ /* Button board card */
+ piu->type = &piuio_dev_bb;
+ } else {
+ /* Full card */
+ piu->type = &piuio_dev_full;
+ }
+
+ /* Allocate input device for generating buttonpresses */
+ idev = input_allocate_device();
+ if (!idev) {
+ kfree(piu->old_inputs);
+ kfree(piu);
+ return ret;
+ }
+
+ /* Initialize PIUIO state and input device */
+ ret = piuio_init(piu, idev, udev);
+ if (ret)
+ goto err;
+
+ piuio_input_init(piu, &intf->dev);
+
+ /* Initialize and register led devices */
+ ret = piuio_leds_init(piu);
+ if (ret)
+ goto err;
+
+ /* Register input device */
+ ret = input_register_device(piu->idev);
+ if (ret) {
+ dev_err(&intf->dev, "piuio probe: failed to register input dev\n");
+ piuio_leds_destroy(piu);
+ goto err;
+ }
+
+ /* Final USB setup */
+ usb_set_intfdata(intf, piu);
+ return 0;
+
+err:
+ piuio_destroy(piu);
+ input_free_device(idev);
+ kfree(piu);
+ return ret;
+}
+
+static void piuio_disconnect(struct usb_interface *intf)
+{
+ struct piuio *piu = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+ if (!piu) {
+ dev_err(&intf->dev, "piuio disconnect: uninitialized device?\n");
+ return;
+ }
+
+ usb_kill_urb(piu->in);
+ usb_kill_urb(piu->out);
+ piuio_leds_destroy(piu);
+ input_unregister_device(piu->idev);
+ piuio_destroy(piu);
+ kfree(piu);
+}
+
+
+/*
+ * USB driver and module definitions
+ */
+static struct usb_device_id piuio_id_table[] = {
+ /* Python WDM2 Encoder used for PIUIO boards */
+ { USB_DEVICE(USB_VENDOR_ID_ANCHOR, USB_PRODUCT_ID_PYTHON2) },
+ /* Special USB ID for button board devices */
+ { USB_DEVICE(USB_VENDOR_ID_BTNBOARD, USB_PRODUCT_ID_BTNBOARD) },
+ {},
+};
+
+MODULE_DEVICE_TABLE(usb, piuio_id_table);
+
+static struct usb_driver piuio_driver = {
+ .name = "piuio",
+ .probe = piuio_probe,
+ .disconnect = piuio_disconnect,
+ .id_table = piuio_id_table,
+};
+
+MODULE_AUTHOR("Devin J. Pohly");
+MODULE_DESCRIPTION("PIUIO input/output driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL v2");
+
+module_usb_driver(piuio_driver);
--
2.16.0
next prev parent reply other threads:[~2018-01-23 19:19 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-01-23 19:20 New input driver for PIUIO input/output board Devin J. Pohly
2018-01-23 19:20 ` Devin J. Pohly [this message]
2018-01-23 19:39 ` [PATCH] Input: add support " Dmitry Torokhov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180123192029.9468-2-djpohly@gmail.com \
--to=djpohly@gmail.com \
--cc=dmitry.torokhov@gmail.com \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.