| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * Driver for loading USB isight firmware | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2008 Matthew Garrett <mjg@redhat.com> | 
|  | 5 | * | 
|  | 6 | * This program is free software; you can redistribute it and/or modify it | 
|  | 7 | * under the terms of the GNU General Public License as published by the Free | 
|  | 8 | * Software Foundation, version 2. | 
|  | 9 | * | 
|  | 10 | * The USB isight cameras in recent Apples are roughly compatible with the USB | 
|  | 11 | * video class specification, and can be driven by uvcvideo. However, they | 
|  | 12 | * need firmware to be loaded beforehand. After firmware loading, the device | 
|  | 13 | * detaches from the USB bus and reattaches with a new device ID. It can then | 
|  | 14 | * be claimed by the uvc driver. | 
|  | 15 | * | 
|  | 16 | * The firmware is non-free and must be extracted by the user. Tools to do this | 
|  | 17 | * are available at http://bersace03.free.fr/ift/ | 
|  | 18 | * | 
|  | 19 | * The isight firmware loading was reverse engineered by Johannes Berg | 
|  | 20 | * <johannes@sipsolutions.de>, and this driver is based on code by Ronald | 
|  | 21 | * Bultje <rbultje@ronald.bitfreak.net> | 
|  | 22 | */ | 
|  | 23 |  | 
|  | 24 | #include <linux/usb.h> | 
|  | 25 | #include <linux/firmware.h> | 
|  | 26 | #include <linux/errno.h> | 
|  | 27 | #include <linux/module.h> | 
|  | 28 |  | 
|  | 29 | static struct usb_device_id id_table[] = { | 
|  | 30 | {USB_DEVICE(0x05ac, 0x8300)}, | 
|  | 31 | {}, | 
|  | 32 | }; | 
|  | 33 |  | 
|  | 34 | MODULE_DEVICE_TABLE(usb, id_table); | 
|  | 35 |  | 
|  | 36 | static int isight_firmware_load(struct usb_interface *intf, | 
|  | 37 | const struct usb_device_id *id) | 
|  | 38 | { | 
|  | 39 | struct usb_device *dev = interface_to_usbdev(intf); | 
|  | 40 | int llen, len, req, ret = 0; | 
|  | 41 | const struct firmware *firmware; | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 42 | unsigned char *buf = kmalloc(50, GFP_KERNEL); | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 43 | unsigned char data[4]; | 
| gregkh@suse.de | ed5a282 | 2008-05-29 10:17:38 -0700 | [diff] [blame] | 44 | const u8 *ptr; | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 45 |  | 
|  | 46 | if (!buf) | 
|  | 47 | return -ENOMEM; | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 48 |  | 
|  | 49 | if (request_firmware(&firmware, "isight.fw", &dev->dev) != 0) { | 
|  | 50 | printk(KERN_ERR "Unable to load isight firmware\n"); | 
| Parag Warudkar | ff1a4a7 | 2008-08-12 15:08:46 -0700 | [diff] [blame] | 51 | ret = -ENODEV; | 
|  | 52 | goto out; | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 53 | } | 
|  | 54 |  | 
|  | 55 | ptr = firmware->data; | 
|  | 56 |  | 
|  | 57 | if (usb_control_msg | 
|  | 58 | (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, "\1", 1, | 
|  | 59 | 300) != 1) { | 
|  | 60 | printk(KERN_ERR | 
|  | 61 | "Failed to initialise isight firmware loader\n"); | 
|  | 62 | ret = -ENODEV; | 
|  | 63 | goto out; | 
|  | 64 | } | 
|  | 65 |  | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 66 | while (ptr+4 <= firmware->data+firmware->size) { | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 67 | memcpy(data, ptr, 4); | 
|  | 68 | len = (data[0] << 8 | data[1]); | 
|  | 69 | req = (data[2] << 8 | data[3]); | 
|  | 70 | ptr += 4; | 
|  | 71 |  | 
|  | 72 | if (len == 0x8001) | 
|  | 73 | break;	/* success */ | 
|  | 74 | else if (len == 0) | 
|  | 75 | continue; | 
|  | 76 |  | 
|  | 77 | for (; len > 0; req += 50) { | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 78 | llen = min(len, 50); | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 79 | len -= llen; | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 80 | if (ptr+llen > firmware->data+firmware->size) { | 
|  | 81 | printk(KERN_ERR | 
|  | 82 | "Malformed isight firmware"); | 
|  | 83 | ret = -ENODEV; | 
|  | 84 | goto out; | 
|  | 85 | } | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 86 | memcpy(buf, ptr, llen); | 
|  | 87 |  | 
|  | 88 | ptr += llen; | 
|  | 89 |  | 
|  | 90 | if (usb_control_msg | 
|  | 91 | (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, req, 0, | 
|  | 92 | buf, llen, 300) != llen) { | 
|  | 93 | printk(KERN_ERR | 
|  | 94 | "Failed to load isight firmware\n"); | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 95 | ret = -ENODEV; | 
|  | 96 | goto out; | 
|  | 97 | } | 
|  | 98 |  | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 99 | } | 
|  | 100 | } | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 101 |  | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 102 | if (usb_control_msg | 
|  | 103 | (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, "\0", 1, | 
|  | 104 | 300) != 1) { | 
|  | 105 | printk(KERN_ERR "isight firmware loading completion failed\n"); | 
|  | 106 | ret = -ENODEV; | 
|  | 107 | } | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 108 |  | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 109 | out: | 
| Matthew Garrett | 62b5884 | 2008-06-06 12:35:15 -0700 | [diff] [blame] | 110 | kfree(buf); | 
| Matthew Garrett | 62d104d | 2008-05-20 20:06:28 +0100 | [diff] [blame] | 111 | release_firmware(firmware); | 
|  | 112 | return ret; | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | static void isight_firmware_disconnect(struct usb_interface *intf) | 
|  | 116 | { | 
|  | 117 | } | 
|  | 118 |  | 
|  | 119 | static struct usb_driver isight_firmware_driver = { | 
|  | 120 | .name = "isight_firmware", | 
|  | 121 | .probe = isight_firmware_load, | 
|  | 122 | .disconnect = isight_firmware_disconnect, | 
|  | 123 | .id_table = id_table, | 
|  | 124 | }; | 
|  | 125 |  | 
|  | 126 | static int __init isight_firmware_init(void) | 
|  | 127 | { | 
|  | 128 | return usb_register(&isight_firmware_driver); | 
|  | 129 | } | 
|  | 130 |  | 
|  | 131 | static void __exit isight_firmware_exit(void) | 
|  | 132 | { | 
|  | 133 | usb_deregister(&isight_firmware_driver); | 
|  | 134 | } | 
|  | 135 |  | 
|  | 136 | module_init(isight_firmware_init); | 
|  | 137 | module_exit(isight_firmware_exit); | 
|  | 138 |  | 
|  | 139 | MODULE_LICENSE("GPL"); | 
|  | 140 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |