From patchwork Sat Jul 28 00:02:34 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yann Cantin X-Patchwork-Id: 1251061 Return-Path: X-Original-To: patchwork-linux-input@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork2.kernel.org (Postfix) with ESMTP id 9AAB5DFFC0 for ; Sat, 28 Jul 2012 00:03:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752811Ab2G1ADG (ORCPT ); Fri, 27 Jul 2012 20:03:06 -0400 Received: from smtp11.smtpout.orange.fr ([80.12.242.133]:43394 "EHLO smtp.smtpout.orange.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752465Ab2G1ADA (ORCPT ); Fri, 27 Jul 2012 20:03:00 -0400 Received: from nestor.ysoft.org ([90.60.77.156]) by mwinf5d21 with ME id fc2w1j0083NLJje03c2xTv; Sat, 28 Jul 2012 02:02:59 +0200 Received: from localhost.ysoft.org (coyote.ysoft.org [192.168.1.100]) by nestor.ysoft.org (Postfix) with ESMTP id 702A0207E3; Sat, 28 Jul 2012 02:02:56 +0200 (CEST) From: Yann Cantin To: linux-input@vger.kernel.org, linux-usb@vger.kernel.org Cc: gregkh@linuxfoundation.org, dmitry.torokhov@gmail.com, linux-kernel@vger.kernel.org, Yann Cantin Subject: [RFC ebeam PATCH 3/3] input: misc: New USB eBeam input driver. Date: Sat, 28 Jul 2012 02:02:34 +0200 Message-Id: <1343433754-3887-4-git-send-email-yann.cantin@laposte.net> X-Mailer: git-send-email 1.7.10 In-Reply-To: <1343433754-3887-1-git-send-email-yann.cantin@laposte.net> References: <1343433754-3887-1-git-send-email-yann.cantin@laposte.net> Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org Signed-off-by: Yann Cantin --- drivers/input/misc/Kconfig | 21 + drivers/input/misc/Makefile | 1 + drivers/input/misc/ebeam.c | 895 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 917 insertions(+) create mode 100644 drivers/input/misc/ebeam.c diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 7faf4a7..0e798cb 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -73,6 +73,27 @@ config INPUT_BMA150 To compile this driver as a module, choose M here: the module will be called bma150. +config INPUT_EBEAM_USB + tristate "USB eBeam driver" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you have a USB eBeam pointing device and want to + use it without any proprietary user space tools. + + Have a look at for + a usage description and the required user-space tools. + + Currently, only the Classic Projection model is supported. + + To compile this driver as a module, choose M here: the + module will be called ebeam. + +config INPUT_EBEAM_USB_CLASSIC + bool "eBeam Classic Projection support" + depends on INPUT_EBEAM_USB + default y + config INPUT_PCSPKR tristate "PC Speaker support" depends on PCSPKR_PLATFORM diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index f55cdf4..4b5e4a9 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o +obj-$(CONFIG_INPUT_EBEAM_USB) += ebeam.o obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o diff --git a/drivers/input/misc/ebeam.c b/drivers/input/misc/ebeam.c new file mode 100644 index 0000000..a18615a --- /dev/null +++ b/drivers/input/misc/ebeam.c @@ -0,0 +1,895 @@ +/****************************************************************************** + * + * eBeam driver + * + * Copyright (C) 2012 Yann Cantin (yann.cantin@laposte.net) + * + * 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. + * + * based on + * + * usbtouchscreen.c by Daniel Ritz + * aiptek.c (sysfs/settings) by Chris Atenasio + * Bryan W. Headley + * + *****************************************************************************/ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "v0.5" +#define DRIVER_AUTHOR "Yann Cantin " +#define DRIVER_DESC "USB eBeam Driver" + +#define USB_VENDOR_ID_EFI 0x2650 /* Electronics For Imaging, Inc */ +#define USB_DEVICE_ID_EFI_CLASSIC 0x1311 /* Classic projection "La banane" */ + +#define EBEAM_BTN_TIP 0x1 /* tip */ +#define EBEAM_BTN_LIT 0x2 /* little */ +#define EBEAM_BTN_BIG 0x4 /* big */ + +/* until KConfig */ +#define CONFIG_INPUT_EBEAM_USB_CLASSIC + +/* device specifc data/functions */ +struct ebeam_device; +struct ebeam_device_info { + int min_X; + int max_X; + int min_Y; + int max_Y; + + /* + * TODO : Check if it's really necessary, waiting for other device info. + * Always service the USB devices irq not just when the input device is + * open. This is useful when devices have a watchdog which prevents us + * from periodically polling the device. Leave this unset unless your + * ebeam device requires it, as it does consume more of the USB + * bandwidth. + */ + bool irq_always; + + int rept_size; + + /* optional, generic exist */ + void (*process_pkt) (struct ebeam_device *ebeam, + unsigned char *pkt, + int len); + + /* mandatory, model-specific */ + int (*read_data) (struct ebeam_device *ebeam, + unsigned char *pkt); + void (*setup_input) (struct ebeam_device *ebeam, + struct input_dev *input_dev); + void (*report_input) (struct ebeam_device *ebeam); + + /* optional, model-specific */ + int (*alloc) (struct ebeam_device *ebeam); + int (*init) (struct ebeam_device *ebeam); + void (*exit) (struct ebeam_device *ebeam); +}; + +/* ebeam settings */ +struct ebeam_settings { + int min_x; + int max_x; + int min_y; + int max_y; + + /* H matrix */ + s64 h1; + s64 h2; + s64 h3; + s64 h4; + s64 h5; + s64 h6; + s64 h7; + s64 h8; + s64 h9; +}; + +/* ebeam device */ +struct ebeam_device { + unsigned char *data; + dma_addr_t data_dma; + unsigned char *buffer; + int buf_len; + struct urb *irq; + struct usb_interface *interface; + struct input_dev *input; + struct ebeam_device_info *type; + char name[128]; + char phys[64]; + void *priv; + + struct ebeam_settings cursetting; /* device's current settings */ + struct ebeam_settings newsetting; /* ... and new ones */ + + bool calibrated; /* false : send raw + * true : send computed */ + u16 X, Y; /* raw coordinates */ + int x, y; /* computed coordinates */ + int btn_map; /* internal buttons map */ +}; + + +/* device types */ +enum { + DEVTYPE_IGNORE = -1, + DEVTYPE_CLASSIC, +}; + +static const struct usb_device_id ebeam_devices[] = { +#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC + {USB_DEVICE(USB_VENDOR_ID_EFI, USB_DEVICE_ID_EFI_CLASSIC), + .driver_info = DEVTYPE_CLASSIC}, +#endif + {} +}; + +/******************************************************************************* + * sysfs part + */ + +/* + * screen min/max and H matrix coefs + * _get return *current* value + * _set set new value + */ +#define DEVICE_MINMAX_ATTR(MM) \ +static ssize_t ebeam_##MM##_get(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ebeam_device *ebeam = dev_get_drvdata(dev); \ + \ + return snprintf(buf, PAGE_SIZE, "%d\n", ebeam->cursetting.MM); \ +} \ +static ssize_t ebeam_##MM##_set(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct ebeam_device *ebeam = dev_get_drvdata(dev); \ + int err, MM; \ + \ + err = kstrtoint(buf, 10, &MM); \ + if (err) \ + return err; \ + \ + ebeam->newsetting.MM = MM; \ + return count; \ +} \ +static DEVICE_ATTR(MM, S_IRUGO | S_IWUGO, \ + ebeam_##MM##_get, \ + ebeam_##MM##_set) + +DEVICE_MINMAX_ATTR(min_x); +DEVICE_MINMAX_ATTR(max_x); +DEVICE_MINMAX_ATTR(min_y); +DEVICE_MINMAX_ATTR(max_y); + +#define DEVICE_H_ATTR(SET_ID) \ +static ssize_t ebeam_h##SET_ID##_get(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct ebeam_device *ebeam = dev_get_drvdata(dev); \ + \ + return snprintf(buf, PAGE_SIZE, "%lld\n", ebeam->cursetting.h##SET_ID);\ +} \ +static ssize_t ebeam_h##SET_ID##_set(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct ebeam_device *ebeam = dev_get_drvdata(dev); \ + int err; \ + u64 h; \ + \ + err = kstrtoll(buf, 10, &h); \ + if (err) \ + return err; \ + \ + ebeam->newsetting.h##SET_ID = h; \ + return count; \ +} \ +static DEVICE_ATTR(h##SET_ID, S_IRUGO | S_IWUGO, \ + ebeam_h##SET_ID##_get, \ + ebeam_h##SET_ID##_set) + +DEVICE_H_ATTR(1); +DEVICE_H_ATTR(2); +DEVICE_H_ATTR(3); +DEVICE_H_ATTR(4); +DEVICE_H_ATTR(5); +DEVICE_H_ATTR(6); +DEVICE_H_ATTR(7); +DEVICE_H_ATTR(8); +DEVICE_H_ATTR(9); + +/* + * sysfs calibrated + * Once H matrix coefs are set, writing 1 to this file triggers + * coordinates mapping. + * Anything else reset the device to un-calibrated mode, + * storing cursetting in newsetting. +*/ +static ssize_t ebeam_calibrated_get(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ebeam_device *ebeam = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", ebeam->calibrated); +} + +static ssize_t ebeam_calibrated_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct ebeam_device *ebeam = dev_get_drvdata(dev); + int err, c; + + err = kstrtoint(buf, 10, &c); + if (err) + return err; + + if (c == 1) { + memcpy(&ebeam->cursetting, &ebeam->newsetting, + sizeof(struct ebeam_settings)); + ebeam->calibrated = true; + ebeam->type->setup_input(ebeam, ebeam->input); + } else { + memcpy(&ebeam->newsetting, &ebeam->cursetting, + sizeof(struct ebeam_settings)); + ebeam->calibrated = false; + ebeam->type->setup_input(ebeam, ebeam->input); + } + + return count; +} + +static DEVICE_ATTR(calibrated, S_IRUGO | S_IWUGO, + ebeam_calibrated_get, ebeam_calibrated_set); + +static struct attribute *ebeam_attrs[] = { + &dev_attr_min_x.attr, + &dev_attr_min_y.attr, + &dev_attr_max_x.attr, + &dev_attr_max_y.attr, + &dev_attr_h1.attr, + &dev_attr_h2.attr, + &dev_attr_h3.attr, + &dev_attr_h4.attr, + &dev_attr_h5.attr, + &dev_attr_h6.attr, + &dev_attr_h7.attr, + &dev_attr_h8.attr, + &dev_attr_h9.attr, + &dev_attr_calibrated.attr, + NULL +}; + +static const struct attribute_group ebeam_attr_group = { + .attrs = ebeam_attrs, +}; + +/******************************************************************************* + * classic projection Part + */ + +#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC +/* IRQ */ +static int classic_read_data(struct ebeam_device *ebeam, unsigned char *pkt) +{ + +/* + * Packet description : 8 bytes + * + * nop packet : FF FF FF FF FF FF FF FF + * + * pkt[0] : Sensors + * bit 1 : ultrasound signal (guessed) + * bit 2 : IR signal (tested with a remote...) ; + * readings OK : 0x03 (anything else is a show-stopper) + * + * pkt[1] : X low + * pkt[2] : X high + * + * pkt[3] : Y low + * pkt[4] : Y high + * + * pkt[5] : fiability ? + * often 0xC0 + * > 0x80 : OK + * + * pkt[6] : + * buttons state (low 4 bits) + * 0x1 = no buttons + * bit 0 : tip (WARNING inversed : 0=pressed) + * bit 1 : ? (always 0 during tests) + * bit 2 : little (1=pressed) + * bit 3 : big (1=pressed) + * + * pointer ID : (hight 4 bits) + * Tested : 0x6=wand ; + * Guessed : 0x1=red ; 0x2=blue ; 0x3=green ; 0x4=black ; + * 0x5=eraser + * bit 4 : pointer ID + * bit 5 : pointer ID + * bit 6 : pointer ID + * bit 7 : pointer ID + * + * + * pkt[7] : fiability ? + * often 0xFF + * + */ + + /* Filtering bad/nop packet */ + if (pkt[0] != 0x03) + return 0; + + ebeam->X = (pkt[2] << 8) | pkt[1]; + ebeam->Y = (pkt[4] << 8) | pkt[3]; + + ebeam->btn_map = (!(pkt[6] & 0x1)) | + ((pkt[6] & 0x4) >> 1) | + ((pkt[6] & 0x8) >> 1); + + return 1; +} + +/* IRQ */ +static void classic_report_input(struct ebeam_device *ebeam) +{ + input_report_key(ebeam->input, BTN_LEFT, + (ebeam->btn_map & EBEAM_BTN_TIP)); + input_report_key(ebeam->input, BTN_MIDDLE, + (ebeam->btn_map & EBEAM_BTN_LIT)); + input_report_key(ebeam->input, BTN_RIGHT, + (ebeam->btn_map & EBEAM_BTN_BIG)); + + input_report_abs(ebeam->input, ABS_X, ebeam->x); + input_report_abs(ebeam->input, ABS_Y, ebeam->y); + + input_sync(ebeam->input); +} + +static void classic_setup_input(struct ebeam_device *ebeam, + struct input_dev *input_dev) +{ + unsigned long flags; + + /* Take event lock while modifying parameters */ + spin_lock_irqsave(&input_dev->event_lock, flags); + + /* Properties */ + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + /* Events generated */ + set_bit(EV_KEY, input_dev->evbit); + set_bit(EV_ABS, input_dev->evbit); + + /* Keys */ + set_bit(BTN_LEFT, input_dev->keybit); + set_bit(BTN_MIDDLE, input_dev->keybit); + set_bit(BTN_RIGHT, input_dev->keybit); + + /* Axis */ + if (!ebeam->calibrated) { + ebeam->cursetting.min_x = ebeam->type->min_X; + ebeam->cursetting.max_x = ebeam->type->max_X; + ebeam->cursetting.min_y = ebeam->type->min_Y; + ebeam->cursetting.max_y = ebeam->type->max_Y; + } + + input_set_abs_params(input_dev, ABS_X, + ebeam->cursetting.min_x, ebeam->cursetting.max_x, + 0, 0); + input_set_abs_params(input_dev, ABS_Y, + ebeam->cursetting.min_y, ebeam->cursetting.max_y, + 0, 0); + + spin_unlock_irqrestore(&input_dev->event_lock, flags); +} + +#endif + +/***************************************************************************** + * device descriptors + */ +static struct ebeam_device_info ebeam_dev_info[] = { +#ifdef CONFIG_INPUT_EBEAM_USB_CLASSIC + [DEVTYPE_CLASSIC] = { + .min_X = 0, + .max_X = 65535, + .min_Y = 0, + .max_Y = 65535, + .rept_size = 8, + .read_data = classic_read_data, + .setup_input = classic_setup_input, + .report_input = classic_report_input, + }, +#endif +}; + +/******************************************************************************* + * Generic Part + * Nothing model-specific below this point + */ + +static void ebeam_init_settings(struct ebeam_device *ebeam) +{ + ebeam->calibrated = false; + + /* Init (x,y) min/max to raw ones */ + ebeam->cursetting.min_x = ebeam->newsetting.min_x = ebeam->type->min_X; + ebeam->cursetting.max_x = ebeam->newsetting.max_x = ebeam->type->max_X; + ebeam->cursetting.min_y = ebeam->newsetting.min_y = ebeam->type->min_Y; + ebeam->cursetting.max_y = ebeam->newsetting.max_y = ebeam->type->max_Y; + + /* Safe values for the H matrix (Identity) */ + ebeam->cursetting.h1 = ebeam->newsetting.h1 = 1; + ebeam->cursetting.h2 = ebeam->newsetting.h2 = 0; + ebeam->cursetting.h3 = ebeam->newsetting.h3 = 0; + + ebeam->cursetting.h4 = ebeam->newsetting.h4 = 0; + ebeam->cursetting.h5 = ebeam->newsetting.h5 = 1; + ebeam->cursetting.h6 = ebeam->newsetting.h6 = 0; + + ebeam->cursetting.h7 = ebeam->newsetting.h7 = 0; + ebeam->cursetting.h8 = ebeam->newsetting.h8 = 0; + ebeam->cursetting.h9 = ebeam->newsetting.h9 = 1; +} + +/* + * IRQ + * compute screen coordinates from raw + * Overflow and negative values are user space's problem + */ +static bool ebeam_calculate_xy(struct ebeam_device *ebeam) +{ + /* TODO : check if u64 division is available on all plateform */ + + s64 scale; + + if (!ebeam->calibrated) { + ebeam->x = ebeam->X; + ebeam->y = ebeam->Y; + } else { + scale = ebeam->cursetting.h7 * ebeam->X + + ebeam->cursetting.h8 * ebeam->Y + + ebeam->cursetting.h9; + + /* Who want a division by zero in kernel ? */ + if (scale == 0) { + dev_err(&(ebeam->interface)->dev, + "%s - Division by zero, wrong calibration.\n", + __func__); + dev_err(&(ebeam->interface)->dev, + "%s - Resetting to un-calibrated mode.\n", + __func__); + ebeam->calibrated = false; + return 0; + } + + /* + * We *must* round the result, but not with (int) (v1/v2 + 0.5) + * + * (int) (v1/v2 + 0.5) <=> (int) ( (2*v1 + v2)/(2*v2) ) + */ + ebeam->x = (int) ((((ebeam->cursetting.h1 * ebeam->X + + ebeam->cursetting.h2 * ebeam->Y + + ebeam->cursetting.h3) << 1) + scale) / + (scale << 1)); + ebeam->y = (int) ((((ebeam->cursetting.h4 * ebeam->X + + ebeam->cursetting.h5 * ebeam->Y + + ebeam->cursetting.h6) << 1) + scale) / + (scale << 1)); + } + + return 1; +} + +/* IRQ */ +/* generic function, may be overloaded */ +static void ebeam_process_pkt(struct ebeam_device *ebeam, + unsigned char *pkt, int len) +{ + struct ebeam_device_info *type = ebeam->type; + + if (!type->read_data(ebeam, pkt)) + return; + + if (!ebeam_calculate_xy(ebeam)) + return; + + type->report_input(ebeam); +} + +/* IRQ + * handler */ +static void ebeam_irq(struct urb *urb) +{ + struct ebeam_device *ebeam = urb->context; + struct device *dev = &ebeam->interface->dev; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ETIME: + /* this urb is timing out */ + dev_dbg(dev, + "%s - urb timed out - was the device unplugged?\n", + __func__); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EPIPE: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", + __func__, urb->status); + goto exit; + } + + ebeam->type->process_pkt(ebeam, ebeam->data, urb->actual_length); + +exit: + usb_mark_last_busy(interface_to_usbdev(ebeam->interface)); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(dev, "%s - usb_submit_urb failed with result: %d\n", + __func__, retval); +} + +static int ebeam_open(struct input_dev *input) +{ + struct ebeam_device *ebeam = input_get_drvdata(input); + int r; + + ebeam->irq->dev = interface_to_usbdev(ebeam->interface); + + r = usb_autopm_get_interface(ebeam->interface) ? -EIO : 0; + if (r < 0) + goto out; + + if (!ebeam->type->irq_always) { + if (usb_submit_urb(ebeam->irq, GFP_KERNEL)) { + r = -EIO; + goto out_put; + } + } + + ebeam->interface->needs_remote_wakeup = 1; +out_put: + usb_autopm_put_interface(ebeam->interface); +out: + return r; +} + +static void ebeam_close(struct input_dev *input) +{ + struct ebeam_device *ebeam = input_get_drvdata(input); + int r; + + if (!ebeam->type->irq_always) + usb_kill_urb(ebeam->irq); + + r = usb_autopm_get_interface(ebeam->interface); + ebeam->interface->needs_remote_wakeup = 0; + + if (!r) + usb_autopm_put_interface(ebeam->interface); +} + +static int ebeam_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct ebeam_device *ebeam = usb_get_intfdata(intf); + + usb_kill_urb(ebeam->irq); + + return 0; +} + +static int ebeam_resume(struct usb_interface *intf) +{ + struct ebeam_device *ebeam = usb_get_intfdata(intf); + struct input_dev *input = ebeam->input; + int result = 0; + + mutex_lock(&input->mutex); + if (input->users || ebeam->type->irq_always) + result = usb_submit_urb(ebeam->irq, GFP_NOIO); + mutex_unlock(&input->mutex); + + return result; +} + +static int ebeam_reset_resume(struct usb_interface *intf) +{ + struct ebeam_device *ebeam = usb_get_intfdata(intf); + struct input_dev *input = ebeam->input; + int err = 0; + + /* reinit the device */ + if (ebeam->type->init) { + err = ebeam->type->init(ebeam); + if (err) { + dev_dbg(&intf->dev, + "%s - type->init() failed, err: %d\n", + __func__, err); + return err; + } + } + + /* restart IO if needed */ + mutex_lock(&input->mutex); + if (input->users) + err = usb_submit_urb(ebeam->irq, GFP_NOIO); + mutex_unlock(&input->mutex); + + return err; +} + +static void ebeam_free_buffers(struct usb_device *udev, + struct ebeam_device *ebeam) +{ + usb_free_coherent(udev, ebeam->type->rept_size, + ebeam->data, ebeam->data_dma); + kfree(ebeam->buffer); +} + +static struct usb_endpoint_descriptor * +ebeam_get_input_endpoint(struct usb_host_interface *interface) +{ + int i; + + for (i = 0; i < interface->desc.bNumEndpoints; i++) + if (usb_endpoint_dir_in(&interface->endpoint[i].desc)) + return &interface->endpoint[i].desc; + + return NULL; +} + +static int ebeam_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct ebeam_device *ebeam; + struct input_dev *input_dev; + struct usb_endpoint_descriptor *endpoint; + struct usb_device *udev = interface_to_usbdev(intf); + struct ebeam_device_info *type; + int err = -ENOMEM; + + /* some devices are ignored */ + if (id->driver_info == DEVTYPE_IGNORE) + return -ENODEV; + + endpoint = ebeam_get_input_endpoint(intf->cur_altsetting); + if (!endpoint) + return -ENXIO; + + ebeam = kzalloc(sizeof(struct ebeam_device), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ebeam || !input_dev) + goto out_free; + + type = &ebeam_dev_info[id->driver_info]; + ebeam->type = type; + ebeam_init_settings(ebeam); + + if (!type->process_pkt) + type->process_pkt = ebeam_process_pkt; + + ebeam->data = usb_alloc_coherent(udev, type->rept_size, + GFP_KERNEL, &ebeam->data_dma); + if (!ebeam->data) + goto out_free; + + ebeam->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!ebeam->irq) { + dev_dbg(&intf->dev, + "%s - usb_alloc_urb failed: ebeam->irq\n", __func__); + goto out_free_buffers; + } + + ebeam->interface = intf; + ebeam->input = input_dev; + + /* setup name */ + snprintf(ebeam->name, sizeof(ebeam->name), + "USB eBeam %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + if (udev->manufacturer || udev->product) { + strlcat(ebeam->name, + " (", + sizeof(ebeam->name)); + + if (udev->manufacturer) + strlcat(ebeam->name, + udev->manufacturer, + sizeof(ebeam->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(ebeam->name, + " ", + sizeof(ebeam->name)); + strlcat(ebeam->name, + udev->product, + sizeof(ebeam->name)); + } + + strlcat(ebeam->name, ")", sizeof(ebeam->name)); + } + + /* usb tree */ + usb_make_path(udev, ebeam->phys, sizeof(ebeam->phys)); + strlcat(ebeam->phys, "/input0", sizeof(ebeam->phys)); + + /* input setup */ + input_dev->name = ebeam->name; + input_dev->phys = ebeam->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, ebeam); + + input_dev->open = ebeam_open; + input_dev->close = ebeam_close; + + /* usb urb setup */ + if (usb_endpoint_type(endpoint) == USB_ENDPOINT_XFER_INT) + usb_fill_int_urb(ebeam->irq, udev, + usb_rcvintpipe(udev, endpoint->bEndpointAddress), + ebeam->data, type->rept_size, + ebeam_irq, ebeam, endpoint->bInterval); + else + usb_fill_bulk_urb(ebeam->irq, udev, + usb_rcvbulkpipe(udev, endpoint->bEndpointAddress), + ebeam->data, type->rept_size, + ebeam_irq, ebeam); + + ebeam->irq->dev = udev; + ebeam->irq->transfer_dma = ebeam->data_dma; + ebeam->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* device specific allocations */ + if (type->alloc) { + err = type->alloc(ebeam); + if (err) { + dev_dbg(&intf->dev, + "%s - type->alloc() failed, err: %d\n", + __func__, err); + goto out_free_urb; + } + } + + /* device specific initialisation */ + if (type->init) { + err = type->init(ebeam); + if (err) { + dev_dbg(&intf->dev, + "%s - type->init() failed, err: %d\n", + __func__, err); + goto out_do_exit; + } + } + + /* input final setup */ + err = input_register_device(ebeam->input); + if (err) { + dev_dbg(&intf->dev, + "%s - input_register_device failed, err: %d\n", + __func__, err); + goto out_do_exit; + } + + type->setup_input(ebeam, input_dev); + + /* usb final setup */ + usb_set_intfdata(intf, ebeam); + + if (ebeam->type->irq_always) { + /* this can't fail */ + usb_autopm_get_interface(intf); + err = usb_submit_urb(ebeam->irq, GFP_KERNEL); + if (err) { + usb_autopm_put_interface(intf); + dev_err(&intf->dev, + "%s - usb_submit_urb failed with result: %d\n", + __func__, err); + goto out_unregister_input; + } + } + + /* sysfs setup */ + err = sysfs_create_group(&intf->dev.kobj, &ebeam_attr_group); + if (err) { + dev_dbg(&intf->dev, + "%s - cannot create sysfs group, err: %d\n", + __func__, err); + goto out_unregister_input; + } + + return 0; + +out_unregister_input: + input_unregister_device(input_dev); + input_dev = NULL; +out_do_exit: + if (type->exit) + type->exit(ebeam); +out_free_urb: + usb_free_urb(ebeam->irq); +out_free_buffers: + ebeam_free_buffers(udev, ebeam); +out_free: + input_free_device(input_dev); + kfree(ebeam); + return err; +} + +static void ebeam_disconnect(struct usb_interface *intf) +{ + struct ebeam_device *ebeam = usb_get_intfdata(intf); + + if (!ebeam) + return; + + dev_dbg(&intf->dev, + "%s - ebeam is initialized, cleaning up\n", __func__); + + usb_set_intfdata(intf, NULL); + /* this will stop IO via close */ + input_unregister_device(ebeam->input); + sysfs_remove_group(&intf->dev.kobj, &ebeam_attr_group); + usb_free_urb(ebeam->irq); + if (ebeam->type->exit) + ebeam->type->exit(ebeam); + ebeam_free_buffers(interface_to_usbdev(intf), ebeam); + kfree(ebeam); +} + +MODULE_DEVICE_TABLE(usb, ebeam_devices); + +static struct usb_driver ebeam_driver = { + .name = "ebeam", + .probe = ebeam_probe, + .disconnect = ebeam_disconnect, + .suspend = ebeam_suspend, + .resume = ebeam_resume, + .reset_resume = ebeam_reset_resume, + .id_table = ebeam_devices, + .supports_autosuspend = 1, +}; + +module_usb_driver(ebeam_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +