Hi, I'm working on a input driver for usb eBeam devices. It's time for a first review, so i brute-post the code below. Tell me if there's a better way to do it, i'm not familiar with the list. BTW, is there a complete "howto contrib driver for noob" somewhere ? Some infos : - It's a pointing device. - http://www.e-beam.com/products/overview.html I work with a "classic" model. - Currently, these devices are only supported on some specific distro via user-space, closed source, daemon and tools, including an xorg input driver. - The device reports positions in it's own cartesian coordinate system, i use an homography to compute screen ones (strange formulas in ebeam_calculate_xy). - code tested against a 3.3.6 kernel (mageia 2). Thanks in advance. ############### /****************************************************************************** * * eBeam driver * * Copyright (C) 2012 Yann Cantin (yann.cantin@xxxxxxxxxxx) * * 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 <daniel.ritz@xxxxxx> * aiptek.c (sysfs/settings) by Chris Atenasio <chris@xxxxxxxx> * Bryan W. Headley <bwheadley@xxxxxxxxxxxxx> * *****************************************************************************/ #define DEBUG #include <linux/kernel.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/module.h> #include <linux/init.h> #include <linux/usb.h> #include <linux/usb/input.h> #include <linux/hid.h> #define DRIVER_VERSION "v0.3" #define DRIVER_AUTHOR "Yann Cantin <yann.cantin@xxxxxxxxxxx>" #define DRIVER_DESC "USB eBeam Driver" #define USB_VENDOR_ID_EFI 0x2650 /* Electronics For Imaging, Inc */ #define USB_PRODUCT_ID_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_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_EBEAM_USB_CLASSIC {USB_DEVICE(USB_VENDOR_ID_EFI, USB_PRODUCT_ID_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; /* TODO : check if we need to suspend irq */ 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_EBEAM_USB_CLASSIC /* IRQ */ static int classic_read_data(struct ebeam_device *ebeam, unsigned char *pkt) { /* * Packet description : * * 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 * 0x1 = no buttons * bit 1 : tip (WARNING inversed : 0=pressed) * bit 2 : ? (always 0 during tests) * bit 3 : little (1=pressed) * bit 4 : big (1=pressed) * * pointer ID : Tested : 0x6=wand ; * Guessed : 0x1=red ; 0x2=blue ; 0x3=green ; * 0x4=black ; 0x5=eraser * bit 5 : pointer ID * bit 6 : pointer ID * bit 7 : pointer ID * bit 8 : 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) { /* 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); } #endif /***************************************************************************** * device descriptors */ static struct ebeam_device_info ebeam_dev_info[] = { #ifdef CONFIG_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"); ############### -- Yann Cantin A4FEB47F -- -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html