diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-05 06:47:02 +0000 |
commit | 99a7e12f34b3661a0d1354eef83a0eef4df5e34c (patch) | |
tree | 3560aca9ca86792f9ab7bd87861ea143a1b3c7a3 /drivers/usb/scanner.c | |
parent | e73a04659c0b8cdee4dd40e58630e2cf63afb316 (diff) |
Merge with Linux 2.3.38.
Diffstat (limited to 'drivers/usb/scanner.c')
-rw-r--r-- | drivers/usb/scanner.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/drivers/usb/scanner.c b/drivers/usb/scanner.c new file mode 100644 index 000000000..806e15ab3 --- /dev/null +++ b/drivers/usb/scanner.c @@ -0,0 +1,509 @@ +/* -*- linux-c -*- */ + +/* + * Driver for USB Scanners (linux-2.3.33) + * + * David E. Nelson (dnelson@jump.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based upon mouse.c (Brad Keryan) and printer.c (Michael Gee). + * + * History + * 0.1 8/31/1999 + * + * Developed/tested using linux-2.3.15 with minor ohci.c changes to + * support short packes during bulk xfer mode. Some testing was + * done with ohci-hcd but the performace was low. Very limited + * testing was performed with uhci but I was unable to get it to + * work. Initial relase to the linux-usb development effort. + * + * 0.2 10/16/1999 + * + * FIXED: + * - Device can't be opened unless a scanner is plugged into the USB. + * - Finally settled on a reasonable value for the I/O buffer's. + * - Cleaned up write_scanner() + * - Disabled read/write stats + * - A little more code cleanup + * + * 0.3 10/18/1999 + * + * FIXED: + * - Device registration changed to reflect new device + * allocation/registration for linux-2.3.22+. + * - Adopted David Brownell's <david-b@pacbell.net> technique for + * assigning bulk endpoints. + * - Removed unnessesary #include's + * - Scanner model now reported via syslog INFO after being detected + * *and* configured. + * - Added user specified verdor:product USB ID's which can be passed + * as module parameters. + * + * 0.3.1 + * FIXED: + * - Applied patches for linux-2.3.25. + * - Error number reporting changed to reflect negative return codes. + * + * 0.3.2 + * FIXED: + * - Applied patches for linux-2.3.26 to scanner_init(). + * - Debug read/write stats now report values as signed decimal. + * + * + * 0.3.3 + * FIXED: + * - Updated the bulk_msg() calls to usb usb_bulk_msg(). + * - Added a small delay in the write_scanner() method to aid in + * avoiding NULL data reads on HP scanners. We'll see how this works. + * - Return values from usb_bulk_msg() now ignore positive values for + * use with the ohci driver. + * - Added conditional debugging instead of commenting/uncommenting + * all over the place. + * - kfree()'d the pointer after using usb_string() as documented in + * linux-usb-api.txt. + * - Added usb_set_configuration(). It got lost in version 0.3 -- ack! + * - Added the HP 5200C USB Vendor/Product ID's + * + * TODO + * - Simultaneous multiple device attachment + * - ioctl()'s ? + * + * Thanks to: + * - All the folks on the linux-usb list who put up with me. :) This + * has been a great learning experience for me. + * - To Linus Torvalds for this great OS. + * - The GNU folks. + * - The folks that forwarded Vendor:Product ID's to me. + * - And anybody else who chimed in with reports and suggestions. + * + * Performance: + * System: Pentium 120, 80 MB RAM, OHCI, Linux 2.3.23, HP 4100C USB Scanner + * 300 dpi scan of the entire bed + * 24 Bit Color ~ 70 secs - 3.6 Mbit/sec + * 8 Bit Gray ~ 17 secs - 4.2 Mbit/sec + * */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <asm/uaccess.h> +#include <linux/malloc.h> +#include <linux/delay.h> + +#undef DEBUG /* Enable to print results of read/write_scanner() calls */ +#undef RD_DATA_DUMP /* Enable to dump data - limited to 24 bytes */ +#undef WR_DATA_DUMP + +#include "usb.h" + +#define IBUF_SIZE 32768 +#define OBUF_SIZE 4096 + +struct hpscan_usb_data { + struct usb_device *hpscan_dev; + int isopen; /* Not zero if the device is open */ + int present; /* Device is present on the bus */ + char *obuf, *ibuf; /* transfer buffers */ + char iep, oep; /* I/O Endpoints */ +}; + +static struct hpscan_usb_data hpscan; + +MODULE_AUTHOR("David E. Nelson, dnelson@jump.net, http://www.jump.net/~dnelson"); +MODULE_DESCRIPTION("USB Scanner Driver"); + +static __u16 vendor=0, product=0; +MODULE_PARM(vendor, "i"); +MODULE_PARM_DESC(vendor, "User specified USB idVendor"); + +MODULE_PARM(product, "i"); +MODULE_PARM_DESC(product, "User specified USB idProduct"); + +static int +open_scanner(struct inode * inode, struct file * file) +{ + struct hpscan_usb_data *hps = &hpscan; + + if (!hps->present) { + return -ENODEV; + } + + if (!hps->hpscan_dev) { + return -ENODEV; + } + + if (hps->isopen) { + return -EBUSY; + } + + hps->isopen = 1; + + MOD_INC_USE_COUNT; + + return 0; +} + +static int +close_scanner(struct inode * inode, struct file * file) +{ + struct hpscan_usb_data *hps = &hpscan; + + hps->isopen = 0; + + MOD_DEC_USE_COUNT; + + return 0; +} + +static ssize_t +write_scanner(struct file * file, const char * buffer, + size_t count, loff_t *ppos) +{ + struct hpscan_usb_data *hps = &hpscan; + + unsigned long copy_size; + unsigned long bytes_written = 0; + unsigned long partial; + + ssize_t ret = 0; + + int result = 0; + + char *obuf = hps->obuf; + + set_current_state(TASK_INTERRUPTIBLE); + + while (count > 0) { + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + copy_size = (count > OBUF_SIZE) ? OBUF_SIZE : count; + + if (copy_from_user(hps->obuf, buffer, copy_size)) { + ret = -EFAULT; + break; + } + + result = usb_bulk_msg(hps->hpscan_dev,usb_sndbulkpipe(hps->hpscan_dev, hps->oep), obuf, copy_size, &partial, 30*HZ); + dbg("write stats: result:%d copy_size:%lu partial:%lu", (int)result, copy_size, partial); + + if (result == USB_ST_TIMEOUT) { /* NAK -- shouldn't happen */ + warn("write_scanner: NAK recieved."); + ret = -ETIME; + break; + } else if (result < 0) { /* We should not get any I/O errors */ + warn("write_scanner: funky result: %d. Please notify the maintainer.", result); + ret = -EIO; + break; + } + +#ifdef WR_DATA_DUMP + if (partial) { + unsigned char cnt, cnt_max; + cnt_max = (partial > 24) ? 24 : partial; + printk(KERN_DEBUG __FILE__ ": dump: "); + for (cnt=0; cnt < cnt_max; cnt++) { + printk("%X ", obuf[cnt]); + } + printk("\n"); + } +#endif + if (partial != copy_size) { /* Unable to write complete amount */ + ret = -EIO; + break; + } + + if (partial) { /* Data written */ + obuf += partial; + count -= partial; + bytes_written += partial; + } else { /* No data written */ + ret = 0; + bytes_written = 0; + break; + } + } +// mdelay(5); + set_current_state(TASK_RUNNING); + return ret ? ret : bytes_written; +} + +static ssize_t +read_scanner(struct file * file, char * buffer, + size_t count, loff_t *ppos) +{ + struct hpscan_usb_data *hps = &hpscan; + + ssize_t read_count, ret = 0; + + unsigned long partial; + + int this_read; + int result; + + char *ibuf = hps->ibuf; + + read_count = 0; + set_current_state(TASK_INTERRUPTIBLE); + + while (count) { + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + this_read = (count > IBUF_SIZE) ? IBUF_SIZE : count; + + result = usb_bulk_msg(hps->hpscan_dev, usb_rcvbulkpipe(hps->hpscan_dev, hps->iep), ibuf, this_read, &partial, 60*HZ); + dbg("read stats: result:%d this_read:%u partial:%lu", (int)result, this_read, partial); + + if (result == USB_ST_TIMEOUT) { /* NAK -- shouldn't happen */ + warn("read_scanner: NAK received"); + ret = -ETIME; + break; + } else if ((result < 0) && (result != USB_ST_DATAUNDERRUN)) { + warn("read_scanner: funky result: %d. Please notify the maintainer.", (int)result); + ret = -EIO; + break; + } + +#ifdef RD_DATA_DUMP + if (partial) { + unsigned char cnt, cnt_max; + cnt_max = (partial > 24) ? 24 : partial; + printk(KERN_DEBUG __FILE__ ": dump: "); + for (cnt=0; cnt < cnt_max; cnt++) { + printk("%X ", ibuf[cnt]); + } + printk("\n"); + } +#endif + + if (partial) { /* Data returned */ + count = this_read = partial; + } else { + ret = 0; + read_count = 0; + break; + } + + if (this_read) { + if (copy_to_user(buffer, ibuf, this_read)) { + ret = -EFAULT; + break; + } + count -= this_read; + read_count += this_read; + buffer += this_read; + } + } + set_current_state(TASK_RUNNING); + return ret ? ret : read_count; +} + +static void * +probe_scanner(struct usb_device *dev, unsigned int ifnum) +{ + struct hpscan_usb_data *hps = &hpscan; + struct usb_endpoint_descriptor *endpoint; + + char *ident; + + hps->present = 0; + + if (vendor != 0 || product != 0) + info("USB Scanner Vendor:Product - %x:%x\n", vendor, product); + +/* There doesn't seem to be an imaging class defined in the USB + * Spec. (yet). If there is, HP isn't following it and it doesn't + * look like anybody else is either. Therefore, we have to test the + * Vendor and Product ID's to see what we have. This makes this + * driver a high maintenance driver since it has to be updated with + * each release of a product. Also, other scanners may be able to use + * this driver but again, their Vendor and Product ID's must be added. + * + * NOTE: Just because a product is supported here does not mean that + * applications exist that support the product. It's in the hopes + * that this will allow developers a means to produce applications + * that will support USB products. + * + * Until we detect a device which is pleasing, we silently punt. + * */ + + if (dev->descriptor.idVendor != 0x03f0 && /* Hewlett Packard */ + dev->descriptor.idVendor != 0x06bd && /* AGFA */ + dev->descriptor.idVendor != 0x1606 && /* UMAX */ + dev->descriptor.idVendor != vendor ) { /* User specified */ + return NULL; + } + + if (dev->descriptor.idProduct != 0x0101 && /* HP 4100C */ + dev->descriptor.idProduct != 0x0102 && /* HP 4200C & PhotoSmart S20? */ + dev->descriptor.idProduct != 0x0202 && /* HP 5100C */ + dev->descriptor.idProduct != 0x0401 && /* HP 5200C */ + dev->descriptor.idProduct != 0x0201 && /* HP 6200C */ + dev->descriptor.idProduct != 0x0601 && /* HP 6300C */ + dev->descriptor.idProduct != 0x0001 && /* AGFA SnapScan 1212U */ + dev->descriptor.idProduct != 0x0030 && /* Umax 2000U */ + dev->descriptor.idProduct != product) { /* User specified */ + return NULL; + } + +/* After this point we can be a little noisy about what we are trying to + * configure. */ + + if (dev->descriptor.bNumConfigurations != 1 || + dev->config[0].bNumInterfaces != 1) { + dbg("probe_scanner: only simple configurations supported"); + return NULL; + } + + endpoint = dev->config[0].interface[0].altsetting[0].endpoint; + + if (endpoint[0].bmAttributes != USB_ENDPOINT_XFER_BULK + || endpoint [1].bmAttributes != USB_ENDPOINT_XFER_BULK) { + dbg("probe_scanner: invalid bulk endpoints"); + return NULL; + } + + if (usb_set_configuration(dev, dev->config[0].bConfigurationValue)) { + dbg("probe_scanner: failed usb_set_configuration"); + hps->hpscan_dev = NULL; + return NULL; + } + +/* By the time we get here, we should be dealing with a fairly simple + * device that supports at least two bulk endpoints on endpoints 1 and + * 2. + * + * We determine the bulk endpoints so that the read_*() and write_*() + * procedures can recv/send data to the correct endpoint. + * */ + + hps->iep = hps->oep = 0; + + if ((endpoint[0].bEndpointAddress & 0x80) == 0x80) { + hps->iep = endpoint[0].bEndpointAddress & 0x7f; + } else { + hps->oep = endpoint[0].bEndpointAddress; + } + + if ((endpoint[1].bEndpointAddress & 0x80) == 0x80) { + hps->iep = endpoint[1].bEndpointAddress & 0x7f; + } else { + hps->oep = endpoint[1].bEndpointAddress; + } + + ident = usb_string(dev, dev->descriptor.iProduct); /* usb_string allocates memory using kmalloc() so kfree() needs to be called afterwards when the pointer is no longer needed. */ + info("USB Scanner (%s) found at address %d", ident, dev->devnum); + kfree(ident); + + dbg("probe_scanner: using bulk endpoints - In: %x Out: %x", hps->iep, hps->oep); + + hps->present = 1; + hps->hpscan_dev = dev; + + if (!(hps->obuf = (char *)kmalloc(OBUF_SIZE, GFP_KERNEL))) { + return NULL; + } + + if (!(hps->ibuf = (char *)kmalloc(IBUF_SIZE, GFP_KERNEL))) { + return NULL; + } + + return hps; +} + +static void +disconnect_scanner(struct usb_device *dev, void *ptr) +{ + struct hpscan_usb_data *hps = (struct hpscan_usb_data *) ptr; + + if (hps->isopen) { + /* better let it finish - the release will do whats needed */ + hps->hpscan_dev = NULL; + return; + } + kfree(hps->ibuf); + kfree(hps->obuf); + + hps->present = 0; +} + +static struct +file_operations usb_scanner_fops = { + NULL, /* seek */ + read_scanner, + write_scanner, + NULL, /* readdir */ + NULL, /* poll */ + NULL, /* ioctl */ + NULL, /* mmap */ + open_scanner, + NULL, /* flush */ + close_scanner, + NULL, + NULL, /* fasync */ +}; + +static struct +usb_driver scanner_driver = { + "usbscanner", + probe_scanner, + disconnect_scanner, + { NULL, NULL }, + &usb_scanner_fops, + 48 +}; + +int +usb_scanner_init(void) +{ + if (usb_register(&scanner_driver) < 0) + return -1; + + info("USB Scanner support registered."); + return 0; +} + + +void +usb_scanner_cleanup(void) +{ + struct hpscan_usb_data *hps = &hpscan; + + hps->present = 0; + usb_deregister(&scanner_driver); +} + +#ifdef MODULE + +int +init_module(void) +{ + return usb_scanner_init(); +} + +void +cleanup_module(void) +{ + usb_scanner_cleanup(); +} +#endif + |