// SPDX-License-Identifier: GPL-2.0+ 
 | 
/* 
 | 
 * Support for the Maxtor OneTouch USB hard drive's button 
 | 
 * 
 | 
 * Current development and maintenance by: 
 | 
 *    Copyright (c) 2005 Nick Sillik <n.sillik@temple.edu> 
 | 
 * 
 | 
 * Initial work by: 
 | 
 *    Copyright (c) 2003 Erik Thyren <erth7411@student.uu.se> 
 | 
 * 
 | 
 * Based on usbmouse.c (Vojtech Pavlik) and xpad.c (Marko Friedemann) 
 | 
 * 
 | 
 */ 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/input.h> 
 | 
#include <linux/slab.h> 
 | 
#include <linux/module.h> 
 | 
#include <linux/usb/input.h> 
 | 
#include "usb.h" 
 | 
#include "debug.h" 
 | 
#include "scsiglue.h" 
 | 
  
 | 
#define DRV_NAME "ums-onetouch" 
 | 
  
 | 
MODULE_DESCRIPTION("Maxtor USB OneTouch hard drive button driver"); 
 | 
MODULE_AUTHOR("Nick Sillik <n.sillik@temple.edu>"); 
 | 
MODULE_LICENSE("GPL"); 
 | 
MODULE_IMPORT_NS(USB_STORAGE); 
 | 
  
 | 
#define ONETOUCH_PKT_LEN        0x02 
 | 
#define ONETOUCH_BUTTON         KEY_PROG1 
 | 
  
 | 
static int onetouch_connect_input(struct us_data *ss); 
 | 
static void onetouch_release_input(void *onetouch_); 
 | 
  
 | 
struct usb_onetouch { 
 | 
    char name[128]; 
 | 
    char phys[64]; 
 | 
    struct input_dev *dev;    /* input device interface */ 
 | 
    struct usb_device *udev;    /* usb device */ 
 | 
  
 | 
    struct urb *irq;    /* urb for interrupt in report */ 
 | 
    unsigned char *data;    /* input data */ 
 | 
    dma_addr_t data_dma; 
 | 
    unsigned int is_open:1; 
 | 
}; 
 | 
  
 | 
  
 | 
/* 
 | 
 * The table of devices 
 | 
 */ 
 | 
#define UNUSUAL_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ 
 | 
            vendorName, productName, useProtocol, useTransport, \ 
 | 
            initFunction, flags) \ 
 | 
{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ 
 | 
  .driver_info = (flags) } 
 | 
  
 | 
static struct usb_device_id onetouch_usb_ids[] = { 
 | 
#    include "unusual_onetouch.h" 
 | 
    { }        /* Terminating entry */ 
 | 
}; 
 | 
MODULE_DEVICE_TABLE(usb, onetouch_usb_ids); 
 | 
  
 | 
#undef UNUSUAL_DEV 
 | 
  
 | 
/* 
 | 
 * The flags table 
 | 
 */ 
 | 
#define UNUSUAL_DEV(idVendor, idProduct, bcdDeviceMin, bcdDeviceMax, \ 
 | 
            vendor_name, product_name, use_protocol, use_transport, \ 
 | 
            init_function, Flags) \ 
 | 
{ \ 
 | 
    .vendorName = vendor_name,    \ 
 | 
    .productName = product_name,    \ 
 | 
    .useProtocol = use_protocol,    \ 
 | 
    .useTransport = use_transport,    \ 
 | 
    .initFunction = init_function,    \ 
 | 
} 
 | 
  
 | 
static struct us_unusual_dev onetouch_unusual_dev_list[] = { 
 | 
#    include "unusual_onetouch.h" 
 | 
    { }        /* Terminating entry */ 
 | 
}; 
 | 
  
 | 
#undef UNUSUAL_DEV 
 | 
  
 | 
  
 | 
static void usb_onetouch_irq(struct urb *urb) 
 | 
{ 
 | 
    struct usb_onetouch *onetouch = urb->context; 
 | 
    signed char *data = onetouch->data; 
 | 
    struct input_dev *dev = onetouch->dev; 
 | 
    int status = urb->status; 
 | 
    int retval; 
 | 
  
 | 
    switch (status) { 
 | 
    case 0:            /* success */ 
 | 
        break; 
 | 
    case -ECONNRESET:    /* unlink */ 
 | 
    case -ENOENT: 
 | 
    case -ESHUTDOWN: 
 | 
        return; 
 | 
    /* -EPIPE:  should clear the halt */ 
 | 
    default:        /* error */ 
 | 
        goto resubmit; 
 | 
    } 
 | 
  
 | 
    input_report_key(dev, ONETOUCH_BUTTON, data[0] & 0x02); 
 | 
    input_sync(dev); 
 | 
  
 | 
resubmit: 
 | 
    retval = usb_submit_urb (urb, GFP_ATOMIC); 
 | 
    if (retval) 
 | 
        dev_err(&dev->dev, "can't resubmit intr, %s-%s/input0, " 
 | 
            "retval %d\n", onetouch->udev->bus->bus_name, 
 | 
            onetouch->udev->devpath, retval); 
 | 
} 
 | 
  
 | 
static int usb_onetouch_open(struct input_dev *dev) 
 | 
{ 
 | 
    struct usb_onetouch *onetouch = input_get_drvdata(dev); 
 | 
  
 | 
    onetouch->is_open = 1; 
 | 
    onetouch->irq->dev = onetouch->udev; 
 | 
    if (usb_submit_urb(onetouch->irq, GFP_KERNEL)) { 
 | 
        dev_err(&dev->dev, "usb_submit_urb failed\n"); 
 | 
        return -EIO; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void usb_onetouch_close(struct input_dev *dev) 
 | 
{ 
 | 
    struct usb_onetouch *onetouch = input_get_drvdata(dev); 
 | 
  
 | 
    usb_kill_urb(onetouch->irq); 
 | 
    onetouch->is_open = 0; 
 | 
} 
 | 
  
 | 
#ifdef CONFIG_PM 
 | 
static void usb_onetouch_pm_hook(struct us_data *us, int action) 
 | 
{ 
 | 
    struct usb_onetouch *onetouch = (struct usb_onetouch *) us->extra; 
 | 
  
 | 
    if (onetouch->is_open) { 
 | 
        switch (action) { 
 | 
        case US_SUSPEND: 
 | 
            usb_kill_urb(onetouch->irq); 
 | 
            break; 
 | 
        case US_RESUME: 
 | 
            if (usb_submit_urb(onetouch->irq, GFP_NOIO) != 0) 
 | 
                dev_err(&onetouch->irq->dev->dev, 
 | 
                    "usb_submit_urb failed\n"); 
 | 
            break; 
 | 
        default: 
 | 
            break; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
#endif /* CONFIG_PM */ 
 | 
  
 | 
static int onetouch_connect_input(struct us_data *ss) 
 | 
{ 
 | 
    struct usb_device *udev = ss->pusb_dev; 
 | 
    struct usb_host_interface *interface; 
 | 
    struct usb_endpoint_descriptor *endpoint; 
 | 
    struct usb_onetouch *onetouch; 
 | 
    struct input_dev *input_dev; 
 | 
    int pipe, maxp; 
 | 
    int error = -ENOMEM; 
 | 
  
 | 
    interface = ss->pusb_intf->cur_altsetting; 
 | 
  
 | 
    if (interface->desc.bNumEndpoints != 3) 
 | 
        return -ENODEV; 
 | 
  
 | 
    endpoint = &interface->endpoint[2].desc; 
 | 
    if (!usb_endpoint_is_int_in(endpoint)) 
 | 
        return -ENODEV; 
 | 
  
 | 
    pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); 
 | 
    maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); 
 | 
    maxp = min(maxp, ONETOUCH_PKT_LEN); 
 | 
  
 | 
    onetouch = kzalloc(sizeof(struct usb_onetouch), GFP_KERNEL); 
 | 
    input_dev = input_allocate_device(); 
 | 
    if (!onetouch || !input_dev) 
 | 
        goto fail1; 
 | 
  
 | 
    onetouch->data = usb_alloc_coherent(udev, ONETOUCH_PKT_LEN, 
 | 
                        GFP_KERNEL, &onetouch->data_dma); 
 | 
    if (!onetouch->data) 
 | 
        goto fail1; 
 | 
  
 | 
    onetouch->irq = usb_alloc_urb(0, GFP_KERNEL); 
 | 
    if (!onetouch->irq) 
 | 
        goto fail2; 
 | 
  
 | 
    onetouch->udev = udev; 
 | 
    onetouch->dev = input_dev; 
 | 
  
 | 
    if (udev->manufacturer) 
 | 
        strlcpy(onetouch->name, udev->manufacturer, 
 | 
            sizeof(onetouch->name)); 
 | 
    if (udev->product) { 
 | 
        if (udev->manufacturer) 
 | 
            strlcat(onetouch->name, " ", sizeof(onetouch->name)); 
 | 
        strlcat(onetouch->name, udev->product, sizeof(onetouch->name)); 
 | 
    } 
 | 
  
 | 
    if (!strlen(onetouch->name)) 
 | 
        snprintf(onetouch->name, sizeof(onetouch->name), 
 | 
             "Maxtor Onetouch %04x:%04x", 
 | 
             le16_to_cpu(udev->descriptor.idVendor), 
 | 
             le16_to_cpu(udev->descriptor.idProduct)); 
 | 
  
 | 
    usb_make_path(udev, onetouch->phys, sizeof(onetouch->phys)); 
 | 
    strlcat(onetouch->phys, "/input0", sizeof(onetouch->phys)); 
 | 
  
 | 
    input_dev->name = onetouch->name; 
 | 
    input_dev->phys = onetouch->phys; 
 | 
    usb_to_input_id(udev, &input_dev->id); 
 | 
    input_dev->dev.parent = &udev->dev; 
 | 
  
 | 
    set_bit(EV_KEY, input_dev->evbit); 
 | 
    set_bit(ONETOUCH_BUTTON, input_dev->keybit); 
 | 
    clear_bit(0, input_dev->keybit); 
 | 
  
 | 
    input_set_drvdata(input_dev, onetouch); 
 | 
  
 | 
    input_dev->open = usb_onetouch_open; 
 | 
    input_dev->close = usb_onetouch_close; 
 | 
  
 | 
    usb_fill_int_urb(onetouch->irq, udev, pipe, onetouch->data, maxp, 
 | 
             usb_onetouch_irq, onetouch, endpoint->bInterval); 
 | 
    onetouch->irq->transfer_dma = onetouch->data_dma; 
 | 
    onetouch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; 
 | 
  
 | 
    ss->extra_destructor = onetouch_release_input; 
 | 
    ss->extra = onetouch; 
 | 
#ifdef CONFIG_PM 
 | 
    ss->suspend_resume_hook = usb_onetouch_pm_hook; 
 | 
#endif 
 | 
  
 | 
    error = input_register_device(onetouch->dev); 
 | 
    if (error) 
 | 
        goto fail3; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
 fail3:    usb_free_urb(onetouch->irq); 
 | 
 fail2:    usb_free_coherent(udev, ONETOUCH_PKT_LEN, 
 | 
              onetouch->data, onetouch->data_dma); 
 | 
 fail1:    kfree(onetouch); 
 | 
    input_free_device(input_dev); 
 | 
    return error; 
 | 
} 
 | 
  
 | 
static void onetouch_release_input(void *onetouch_) 
 | 
{ 
 | 
    struct usb_onetouch *onetouch = (struct usb_onetouch *) onetouch_; 
 | 
  
 | 
    if (onetouch) { 
 | 
        usb_kill_urb(onetouch->irq); 
 | 
        input_unregister_device(onetouch->dev); 
 | 
        usb_free_urb(onetouch->irq); 
 | 
        usb_free_coherent(onetouch->udev, ONETOUCH_PKT_LEN, 
 | 
                  onetouch->data, onetouch->data_dma); 
 | 
    } 
 | 
} 
 | 
  
 | 
static struct scsi_host_template onetouch_host_template; 
 | 
  
 | 
static int onetouch_probe(struct usb_interface *intf, 
 | 
             const struct usb_device_id *id) 
 | 
{ 
 | 
    struct us_data *us; 
 | 
    int result; 
 | 
  
 | 
    result = usb_stor_probe1(&us, intf, id, 
 | 
            (id - onetouch_usb_ids) + onetouch_unusual_dev_list, 
 | 
            &onetouch_host_template); 
 | 
    if (result) 
 | 
        return result; 
 | 
  
 | 
    /* Use default transport and protocol */ 
 | 
  
 | 
    result = usb_stor_probe2(us); 
 | 
    return result; 
 | 
} 
 | 
  
 | 
static struct usb_driver onetouch_driver = { 
 | 
    .name =        DRV_NAME, 
 | 
    .probe =    onetouch_probe, 
 | 
    .disconnect =    usb_stor_disconnect, 
 | 
    .suspend =    usb_stor_suspend, 
 | 
    .resume =    usb_stor_resume, 
 | 
    .reset_resume =    usb_stor_reset_resume, 
 | 
    .pre_reset =    usb_stor_pre_reset, 
 | 
    .post_reset =    usb_stor_post_reset, 
 | 
    .id_table =    onetouch_usb_ids, 
 | 
    .soft_unbind =    1, 
 | 
    .no_dynamic_id = 1, 
 | 
}; 
 | 
  
 | 
module_usb_stor_driver(onetouch_driver, onetouch_host_template, DRV_NAME); 
 |