162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for loading USB isight firmware
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2008 Matthew Garrett <mjg@redhat.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * The USB isight cameras in recent Apples are roughly compatible with the USB
862306a36Sopenharmony_ci * video class specification, and can be driven by uvcvideo. However, they
962306a36Sopenharmony_ci * need firmware to be loaded beforehand. After firmware loading, the device
1062306a36Sopenharmony_ci * detaches from the USB bus and reattaches with a new device ID. It can then
1162306a36Sopenharmony_ci * be claimed by the uvc driver.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * The firmware is non-free and must be extracted by the user. Tools to do this
1462306a36Sopenharmony_ci * are available at http://bersace03.free.fr/ift/
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * The isight firmware loading was reverse engineered by Johannes Berg
1762306a36Sopenharmony_ci * <johannes@sipsolutions.de>, and this driver is based on code by Ronald
1862306a36Sopenharmony_ci * Bultje <rbultje@ronald.bitfreak.net>
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/usb.h>
2262306a36Sopenharmony_ci#include <linux/firmware.h>
2362306a36Sopenharmony_ci#include <linux/errno.h>
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic const struct usb_device_id id_table[] = {
2862306a36Sopenharmony_ci	{USB_DEVICE(0x05ac, 0x8300)},
2962306a36Sopenharmony_ci	{},
3062306a36Sopenharmony_ci};
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, id_table);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic int isight_firmware_load(struct usb_interface *intf,
3562306a36Sopenharmony_ci				const struct usb_device_id *id)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct usb_device *dev = interface_to_usbdev(intf);
3862306a36Sopenharmony_ci	int llen, len, req, ret = 0;
3962306a36Sopenharmony_ci	const struct firmware *firmware;
4062306a36Sopenharmony_ci	unsigned char *buf = kmalloc(50, GFP_KERNEL);
4162306a36Sopenharmony_ci	unsigned char data[4];
4262306a36Sopenharmony_ci	const u8 *ptr;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (!buf)
4562306a36Sopenharmony_ci		return -ENOMEM;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (request_firmware(&firmware, "isight.fw", &dev->dev) != 0) {
4862306a36Sopenharmony_ci		printk(KERN_ERR "Unable to load isight firmware\n");
4962306a36Sopenharmony_ci		ret = -ENODEV;
5062306a36Sopenharmony_ci		goto out;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	ptr = firmware->data;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	buf[0] = 0x01;
5662306a36Sopenharmony_ci	if (usb_control_msg
5762306a36Sopenharmony_ci	    (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1,
5862306a36Sopenharmony_ci	     300) != 1) {
5962306a36Sopenharmony_ci		printk(KERN_ERR
6062306a36Sopenharmony_ci		       "Failed to initialise isight firmware loader\n");
6162306a36Sopenharmony_ci		ret = -ENODEV;
6262306a36Sopenharmony_ci		goto out;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	while (ptr+4 <= firmware->data+firmware->size) {
6662306a36Sopenharmony_ci		memcpy(data, ptr, 4);
6762306a36Sopenharmony_ci		len = (data[0] << 8 | data[1]);
6862306a36Sopenharmony_ci		req = (data[2] << 8 | data[3]);
6962306a36Sopenharmony_ci		ptr += 4;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (len == 0x8001)
7262306a36Sopenharmony_ci			break;	/* success */
7362306a36Sopenharmony_ci		else if (len == 0)
7462306a36Sopenharmony_ci			continue;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci		for (; len > 0; req += 50) {
7762306a36Sopenharmony_ci			llen = min(len, 50);
7862306a36Sopenharmony_ci			len -= llen;
7962306a36Sopenharmony_ci			if (ptr+llen > firmware->data+firmware->size) {
8062306a36Sopenharmony_ci				printk(KERN_ERR
8162306a36Sopenharmony_ci				       "Malformed isight firmware");
8262306a36Sopenharmony_ci				ret = -ENODEV;
8362306a36Sopenharmony_ci				goto out;
8462306a36Sopenharmony_ci			}
8562306a36Sopenharmony_ci			memcpy(buf, ptr, llen);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci			ptr += llen;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci			if (usb_control_msg
9062306a36Sopenharmony_ci			    (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, req, 0,
9162306a36Sopenharmony_ci			     buf, llen, 300) != llen) {
9262306a36Sopenharmony_ci				printk(KERN_ERR
9362306a36Sopenharmony_ci				       "Failed to load isight firmware\n");
9462306a36Sopenharmony_ci				ret = -ENODEV;
9562306a36Sopenharmony_ci				goto out;
9662306a36Sopenharmony_ci			}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		}
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	buf[0] = 0x00;
10262306a36Sopenharmony_ci	if (usb_control_msg
10362306a36Sopenharmony_ci	    (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1,
10462306a36Sopenharmony_ci	     300) != 1) {
10562306a36Sopenharmony_ci		printk(KERN_ERR "isight firmware loading completion failed\n");
10662306a36Sopenharmony_ci		ret = -ENODEV;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ciout:
11062306a36Sopenharmony_ci	kfree(buf);
11162306a36Sopenharmony_ci	release_firmware(firmware);
11262306a36Sopenharmony_ci	return ret;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ciMODULE_FIRMWARE("isight.fw");
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic void isight_firmware_disconnect(struct usb_interface *intf)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic struct usb_driver isight_firmware_driver = {
12262306a36Sopenharmony_ci	.name = "isight_firmware",
12362306a36Sopenharmony_ci	.probe = isight_firmware_load,
12462306a36Sopenharmony_ci	.disconnect = isight_firmware_disconnect,
12562306a36Sopenharmony_ci	.id_table = id_table,
12662306a36Sopenharmony_ci};
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cimodule_usb_driver(isight_firmware_driver);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
13162306a36Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
132