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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).