X-Git-Url: https://git.ao2.it/libam7xxx.git/blobdiff_plain/521c3915354a7119b67969fe9824b31710d732ca..9e24b798ba1376655756c55fd157661148799422:/src/am7xxx.c

diff --git a/src/am7xxx.c b/src/am7xxx.c
index e809c55..94b01ac 100644
--- a/src/am7xxx.c
+++ b/src/am7xxx.c
@@ -1,6 +1,6 @@
 /* am7xxx - communication with AM7xxx based USB Pico Projectors and DPFs
  *
- * Copyright (C) 2012  Antonio Ospite <ospite@studenti.unina.it>
+ * Copyright (C) 2012-2014  Antonio Ospite <ao2@ao2.it>
  *
  * 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
@@ -26,6 +26,7 @@
 
 #include "am7xxx.h"
 #include "serialize.h"
+#include "tools.h"
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
@@ -33,7 +34,7 @@
  * taken from: http://unixwiz.net/techtips/gnu-c-attributes.html)
  */
 #ifndef __GNUC__
-#  define  __attribute__(x)  /*NOTHING*/
+#  define  __attribute__(x)  /* NOTHING */
 #endif
 
 /* Control shared library symbols visibility */
@@ -64,37 +65,80 @@ static void log_message(am7xxx_context *ctx,
 #define debug(ctx, ...)   log_message(ctx,  AM7XXX_LOG_DEBUG,   __func__, 0,        __VA_ARGS__)
 #define trace(ctx, ...)   log_message(ctx,  AM7XXX_LOG_TRACE,   NULL,     0,        __VA_ARGS__)
 
+struct am7xxx_ops {
+	int (*set_power_mode)(am7xxx_device *dev, am7xxx_power_mode power);
+	int (*set_zoom_mode)(am7xxx_device *dev, am7xxx_zoom_mode zoom);
+};
+
 struct am7xxx_usb_device_descriptor {
 	const char *name;
 	uint16_t vendor_id;
 	uint16_t product_id;
+	uint8_t configuration;    /* The bConfigurationValue of the device */
+	uint8_t interface_number; /* The bInterfaceNumber of the device */
+	struct am7xxx_ops ops;
 };
 
-static struct am7xxx_usb_device_descriptor supported_devices[] = {
+static int default_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power);
+static int picopix_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power);
+static int default_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom);
+static int picopix_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom);
+
+#define DEFAULT_OPS { \
+	.set_power_mode = default_set_power_mode, \
+	.set_zoom_mode = default_set_zoom_mode, \
+}
+
+static const struct am7xxx_usb_device_descriptor supported_devices[] = {
 	{
 		.name       = "Acer C110",
 		.vendor_id  = 0x1de1,
 		.product_id = 0xc101,
+		.configuration    = 2,
+		.interface_number = 0,
+		.ops = DEFAULT_OPS,
 	},
 	{
 		.name       = "Acer C112",
 		.vendor_id  = 0x1de1,
 		.product_id = 0x5501,
+		.configuration    = 2,
+		.interface_number = 0,
+		.ops = DEFAULT_OPS,
 	},
 	{
 		.name       ="Aiptek PocketCinema T25",
 		.vendor_id  = 0x08ca,
 		.product_id = 0x2144,
+		.configuration    = 2,
+		.interface_number = 0,
+		.ops = DEFAULT_OPS,
 	},
 	{
 		.name       = "Philips/Sagemcom PicoPix 1020",
 		.vendor_id  = 0x21e7,
 		.product_id = 0x000e,
+		.configuration    = 2,
+		.interface_number = 0,
+		.ops = DEFAULT_OPS,
 	},
 	{
 		.name       = "Philips/Sagemcom PicoPix 2055",
 		.vendor_id  = 0x21e7,
 		.product_id = 0x0016,
+		.configuration    = 2,
+		.interface_number = 0,
+		.ops = {
+			.set_power_mode = picopix_set_power_mode,
+			.set_zoom_mode = picopix_set_zoom_mode,
+		},
+	},
+	{
+		.name       = "Philips/Sagemcom PicoPix 2330",
+		.vendor_id  = 0x21e7,
+		.product_id = 0x0019,
+		.configuration    = 1,
+		.interface_number = 0,
 	},
 };
 
@@ -106,6 +150,8 @@ static struct am7xxx_usb_device_descriptor supported_devices[] = {
 
 struct _am7xxx_device {
 	libusb_device_handle *usb_device;
+	struct libusb_transfer *transfer;
+	int transfer_completed;
 	uint8_t buffer[AM7XXX_HEADER_WIRE_SIZE];
 	am7xxx_device_info *device_info;
 	am7xxx_context *ctx;
@@ -124,6 +170,11 @@ typedef enum {
 	AM7XXX_PACKET_TYPE_IMAGE   = 0x02,
 	AM7XXX_PACKET_TYPE_POWER   = 0x04,
 	AM7XXX_PACKET_TYPE_ZOOM    = 0x05,
+	AM7XXX_PACKET_TYPE_PICOPIX_POWER_LOW    = 0x15,
+	AM7XXX_PACKET_TYPE_PICOPIX_POWER_MEDIUM = 0x16,
+	AM7XXX_PACKET_TYPE_PICOPIX_POWER_HIGH   = 0x17,
+	AM7XXX_PACKET_TYPE_PICOPIX_ENABLE_TI    = 0x18,
+	AM7XXX_PACKET_TYPE_PICOPIX_DISABLE_TI   = 0x19,
 } am7xxx_packet_type;
 
 struct am7xxx_generic_header {
@@ -189,6 +240,18 @@ struct am7xxx_header {
 
 
 #ifdef DEBUG
+static void debug_dump_generic_header(am7xxx_context *ctx, struct am7xxx_generic_header *g)
+{
+	if (ctx == NULL || g == NULL)
+		return;
+
+	debug(ctx, "Generic header:\n");
+	debug(ctx, "\tfield0:  0x%08x (%u)\n", g->field0, g->field0);
+	debug(ctx, "\tfield1:  0x%08x (%u)\n", g->field1, g->field1);
+	debug(ctx, "\tfield2:  0x%08x (%u)\n", g->field2, g->field2);
+	debug(ctx, "\tfield3:  0x%08x (%u)\n", g->field3, g->field3);
+}
+
 static void debug_dump_devinfo_header(am7xxx_context *ctx, struct am7xxx_devinfo_header *d)
 {
 	if (ctx == NULL || d == NULL)
@@ -267,7 +330,8 @@ static void debug_dump_header(am7xxx_context *ctx, struct am7xxx_header *h)
 		break;
 
 	default:
-		debug(ctx, "Packet type not supported!\n");
+		debug(ctx, "Parsing data not supported for this packet type!\n");
+		debug_dump_generic_header(ctx, &(h->header_data.data));
 		break;
 	}
 	debug(ctx, "END\n\n");
@@ -321,8 +385,8 @@ static int read_data(am7xxx_device *dev, uint8_t *buffer, unsigned int len)
 
 	ret = libusb_bulk_transfer(dev->usb_device, 0x81, buffer, len, &transferred, 0);
 	if (ret != 0 || (unsigned int)transferred != len) {
-		error(dev->ctx, "ret: %d\ttransferred: %d (expected %u)\n",
-		      ret, transferred, len);
+		error(dev->ctx, "%s. Transferred: %d (expected %u)\n",
+		      libusb_error_name(ret), transferred, len);
 		return ret;
 	}
 
@@ -340,14 +404,125 @@ static int send_data(am7xxx_device *dev, uint8_t *buffer, unsigned int len)
 
 	ret = libusb_bulk_transfer(dev->usb_device, 0x1, buffer, len, &transferred, 0);
 	if (ret != 0 || (unsigned int)transferred != len) {
-		error(dev->ctx, "ret: %d\ttransferred: %d (expected %u)\n",
-		      ret, transferred, len);
+		error(dev->ctx, "%s. Transferred: %d (expected %u)\n",
+		      libusb_error_name(ret), transferred, len);
 		return ret;
 	}
 
 	return 0;
 }
 
+static void send_data_async_complete_cb(struct libusb_transfer *transfer)
+{
+	am7xxx_device *dev = (am7xxx_device *)(transfer->user_data);
+	int *completed = &(dev->transfer_completed);
+	int transferred = transfer->actual_length;
+	int ret;
+
+	if (transferred != transfer->length) {
+		error(dev->ctx, "transferred: %d (expected %u)\n",
+		      transferred, transfer->length);
+	}
+
+	switch (transfer->status) {
+	case LIBUSB_TRANSFER_COMPLETED:
+		ret = 0;
+		break;
+	case LIBUSB_TRANSFER_TIMED_OUT:
+		ret = LIBUSB_ERROR_TIMEOUT;
+		break;
+	case LIBUSB_TRANSFER_STALL:
+		ret = LIBUSB_ERROR_PIPE;
+		break;
+	case LIBUSB_TRANSFER_OVERFLOW:
+		ret = LIBUSB_ERROR_OVERFLOW;
+		break;
+	case LIBUSB_TRANSFER_NO_DEVICE:
+		ret = LIBUSB_ERROR_NO_DEVICE;
+		break;
+	case LIBUSB_TRANSFER_ERROR:
+	case LIBUSB_TRANSFER_CANCELLED:
+		ret = LIBUSB_ERROR_IO;
+		break;
+	default:
+		error(dev->ctx, "unrecognised status code %d", transfer->status);
+		ret = LIBUSB_ERROR_OTHER;
+	}
+
+	if (ret < 0)
+		error(dev->ctx, "libusb transfer failed: %s",
+		      libusb_error_name(ret));
+
+	libusb_free_transfer(transfer);
+	transfer = NULL;
+
+	*completed = 1;
+}
+
+static inline void wait_for_trasfer_completed(am7xxx_device *dev)
+{
+	while (!dev->transfer_completed) {
+		int ret = libusb_handle_events_completed(dev->ctx->usb_context,
+							 &(dev->transfer_completed));
+		if (ret < 0) {
+			if (ret == LIBUSB_ERROR_INTERRUPTED)
+				continue;
+			error(dev->ctx, "libusb_handle_events failed: %s, cancelling transfer and retrying",
+			      libusb_error_name(ret));
+			libusb_cancel_transfer(dev->transfer);
+			continue;
+		}
+	}
+}
+
+static int send_data_async(am7xxx_device *dev, uint8_t *buffer, unsigned int len)
+{
+	int ret;
+	uint8_t *transfer_buffer;
+
+	dev->transfer = libusb_alloc_transfer(0);
+	if (dev->transfer == NULL) {
+		error(dev->ctx, "cannot allocate transfer (%s)\n",
+		      strerror(errno));
+		return -ENOMEM;
+	}
+
+	/* Make a copy of the buffer so the caller can safely reuse it just
+	 * after libusb_submit_transfer() has returned. This technique
+	 * requires more dynamic allocations compared to a proper
+	 * double-buffering approach but it takes a lot less code. */
+	transfer_buffer = malloc(len);
+	if (transfer_buffer == NULL) {
+		error(dev->ctx, "cannot allocate transfer buffer (%s)\n",
+		      strerror(errno));
+		ret = -ENOMEM;
+		goto err;
+	}
+	memcpy(transfer_buffer, buffer, len);
+
+	dev->transfer->flags |= LIBUSB_TRANSFER_FREE_BUFFER;
+	libusb_fill_bulk_transfer(dev->transfer, dev->usb_device, 0x1,
+				  transfer_buffer, len,
+				  send_data_async_complete_cb, dev, 0);
+
+	/* wait for the previous transfer to complete */
+	wait_for_trasfer_completed(dev);
+
+	trace_dump_buffer(dev->ctx, "sending -->", buffer, len);
+
+	dev->transfer_completed = 0;
+	ret = libusb_submit_transfer(dev->transfer);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+
+err:
+	libusb_free_transfer(dev->transfer);
+	dev->transfer = NULL;
+	return ret;
+}
+
 static void serialize_header(struct am7xxx_header *h, uint8_t *buffer)
 {
 	uint8_t **buffer_iterator = &buffer;
@@ -423,6 +598,28 @@ static int send_header(am7xxx_device *dev, struct am7xxx_header *h)
 	return ret;
 }
 
+static int send_command(am7xxx_device *dev, am7xxx_packet_type type)
+{
+	struct am7xxx_header h = {
+		.packet_type     = type,
+		.direction       = AM7XXX_DIRECTION_OUT,
+		.header_data_len = 0x00,
+		.unknown2        = 0x3e,
+		.unknown3        = 0x10,
+		.header_data = {
+			.data = {
+				.field0 = 0,
+				.field1 = 0,
+				.field2 = 0,
+				.field3 = 0,
+			},
+		},
+	};
+
+	return send_header(dev, &h);
+}
+
+
 /* When level == AM7XXX_LOG_FATAL do not check the log_level from the context
  * and print the message unconditionally, this makes it possible to print
  * fatal messages even early on initialization, before the context has been
@@ -472,6 +669,7 @@ static am7xxx_device *add_new_device(am7xxx_context *ctx,
 
 	new_device->ctx = ctx;
 	new_device->desc = desc;
+	new_device->transfer_completed = 1;
 
 	devices_list = &(ctx->devices_list);
 
@@ -504,6 +702,120 @@ static am7xxx_device *find_device(am7xxx_context *ctx,
 	return current;
 }
 
+static int open_device(am7xxx_context *ctx,
+		       unsigned int device_index,
+		       libusb_device *usb_dev,
+		       am7xxx_device **dev)
+{
+	int ret;
+	int current_configuration;
+
+	*dev = find_device(ctx, device_index);
+	if (*dev == NULL) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/* the usb device has already been opened */
+	if ((*dev)->usb_device) {
+		debug(ctx, "(*dev)->usb_device already set\n");
+		ret = 1;
+		goto out;
+	}
+
+	ret = libusb_open(usb_dev, &((*dev)->usb_device));
+	if (ret < 0) {
+		debug(ctx, "libusb_open failed: %s\n", libusb_error_name(ret));
+		goto out;
+	}
+
+	/* XXX, the device is now open, if any of the calls below fail we need
+	 * to close it again before bailing out.
+	 */
+
+	current_configuration = -1;
+	ret = libusb_get_configuration((*dev)->usb_device,
+				       &current_configuration);
+	if (ret < 0) {
+		debug(ctx, "libusb_get_configuration failed: %s\n",
+		      libusb_error_name(ret));
+		goto out_libusb_close;
+	}
+
+	if (current_configuration != (*dev)->desc->configuration) {
+		/*
+		 * In principle, before setting a new configuration, kernel
+		 * drivers should be detached from _all_ interfaces; for
+		 * example calling something like the following "invented"
+		 * function _before_ setting the new configuration:
+		 *
+		 *   libusb_detach_all_kernel_drivers((*dev)->usb_device);
+		 *
+		 * However, in practice, this is not necessary for most
+		 * devices as they have only one configuration.
+		 *
+		 * When a device only has one configuration:
+		 *
+		 *   - if there was a kernel driver bound to the device, it
+		 *     had already set the configuration and the call below
+		 *     will be skipped;
+		 *
+		 *   - if no kernel driver was bound to the device, the call
+		 *     below will suceed.
+		 */
+		ret = libusb_set_configuration((*dev)->usb_device,
+					       (*dev)->desc->configuration);
+		if (ret < 0) {
+			debug(ctx, "libusb_set_configuration failed: %s\n",
+			      libusb_error_name(ret));
+			debug(ctx, "Cannot set configuration %hhu\n",
+			      (*dev)->desc->configuration);
+			goto out_libusb_close;
+		}
+	}
+
+	libusb_set_auto_detach_kernel_driver((*dev)->usb_device, 1);
+
+	ret = libusb_claim_interface((*dev)->usb_device,
+				     (*dev)->desc->interface_number);
+	if (ret < 0) {
+		debug(ctx, "libusb_claim_interface failed: %s\n",
+		      libusb_error_name(ret));
+		debug(ctx, "Cannot claim interface %hhu\n",
+		      (*dev)->desc->interface_number);
+		goto out_libusb_close;
+	}
+
+	/* Checking that the configuration has not changed, as suggested in
+	 * http://libusb.sourceforge.net/api-1.0/caveats.html
+	 */
+	current_configuration = -1;
+	ret = libusb_get_configuration((*dev)->usb_device,
+				       &current_configuration);
+	if (ret < 0) {
+		debug(ctx, "libusb_get_configuration after claim failed: %s\n",
+		      libusb_error_name(ret));
+		goto out_libusb_release_interface;
+	}
+
+	if (current_configuration != (*dev)->desc->configuration) {
+		debug(ctx, "libusb configuration changed (expected: %hhu, current: %hhu\n",
+		      (*dev)->desc->configuration, current_configuration);
+		ret = -EINVAL;
+		goto out_libusb_release_interface;
+	}
+out:
+	return ret;
+
+out_libusb_release_interface:
+	libusb_release_interface((*dev)->usb_device,
+				 (*dev)->desc->interface_number);
+out_libusb_close:
+	libusb_close((*dev)->usb_device);
+	(*dev)->usb_device = NULL;
+	return ret;
+}
+
 typedef enum {
 	SCAN_OP_BUILD_DEVLIST,
 	SCAN_OP_OPEN_DEVICE,
@@ -513,13 +825,14 @@ typedef enum {
  * This is where the central logic of multi-device support is.
  *
  * When 'op' == SCAN_OP_BUILD_DEVLIST the parameters 'open_device_index' and
- * 'dev' are ignored; the function returns 0 on success and a negative value
+ * 'dev' are ignored; the function returns 0 on success or a negative value
  * on error.
  *
  * When 'op' == SCAN_OP_OPEN_DEVICE the function opens the supported USB
  * device with index 'open_device_index' and returns the correspondent
- * am7xxx_device in the 'dev' parameter; the function returns 0 on success,
- * 1 if the device was already open and a negative value on error.
+ * am7xxx_device in the 'dev' parameter; the function returns the value from
+ * open_device(), which is 0 on success, 1 if the device was already open or
+ * a negative value on error.
  *
  * NOTES:
  * if scan_devices() fails when called with 'op' == SCAN_OP_BUILD_DEVLIST,
@@ -529,8 +842,8 @@ typedef enum {
 static int scan_devices(am7xxx_context *ctx, scan_op op,
 			unsigned int open_device_index, am7xxx_device **dev)
 {
-	int num_devices;
-	libusb_device** list;
+	ssize_t num_devices;
+	libusb_device **list;
 	unsigned int current_index;
 	int i;
 	int ret;
@@ -582,47 +895,16 @@ static int scan_devices(am7xxx_context *ctx, scan_op op,
 				} else if (op == SCAN_OP_OPEN_DEVICE &&
 					   current_index == open_device_index) {
 
-					*dev = find_device(ctx, open_device_index);
-					if (*dev == NULL) {
-						ret = -ENODEV;
-						goto out;
-					}
-
-					/* the usb device has already been opened */
-					if ((*dev)->usb_device) {
-						debug(ctx, "(*dev)->usb_device already set\n");
-						ret = 1;
-						goto out;
-					}
-
-					ret = libusb_open(list[i], &((*dev)->usb_device));
-					if (ret < 0) {
-						debug(ctx, "libusb_open failed\n");
-						goto out;
-					}
-
-					/* XXX, the device is now open, if any
-					 * of the calls below fail we need to
-					 * close it again before bailing out.
-					 */
-
-					ret = libusb_set_configuration((*dev)->usb_device, 2);
-					if (ret < 0) {
-						debug(ctx, "libusb_set_configuration failed\n");
-						debug(ctx, "Cannot set configuration 2\n");
-						goto out_libusb_close;
-					}
-
-					ret = libusb_claim_interface((*dev)->usb_device, 0);
-					if (ret < 0) {
-						debug(ctx, "libusb_claim_interface failed\n");
-						debug(ctx, "Cannot claim interface 0\n");
-out_libusb_close:
-						libusb_close((*dev)->usb_device);
-						(*dev)->usb_device = NULL;
-						goto out;
-					}
+					ret = open_device(ctx,
+							  open_device_index,
+							  list[i],
+							  dev);
+					if (ret < 0)
+						debug(ctx, "open_device failed\n");
 
+					/* exit the loop unconditionally after
+					 * attempting to open the device
+					 * requested by the user */
 					goto out;
 				}
 				current_index++;
@@ -630,7 +912,8 @@ out_libusb_close:
 		}
 	}
 
-	/* if we made it up to here we didn't find any device to open */
+	/* if we made it up to here when op == SCAN_OP_OPEN_DEVICE,
+	 * no devices to open had been found. */
 	if (op == SCAN_OP_OPEN_DEVICE) {
 		error(ctx, "Cannot find any device to open\n");
 		ret = -ENODEV;
@@ -644,11 +927,167 @@ out:
 	return ret;
 }
 
+/* Device specific operations */
+
+static int default_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
+{
+	int ret;
+	struct am7xxx_header h = {
+		.packet_type     = AM7XXX_PACKET_TYPE_POWER,
+		.direction       = AM7XXX_DIRECTION_OUT,
+		.header_data_len = sizeof(struct am7xxx_power_header),
+		.unknown2        = 0x3e,
+		.unknown3        = 0x10,
+	};
+
+	switch(power) {
+	case AM7XXX_POWER_OFF:
+		h.header_data.power.bit2 = 0;
+		h.header_data.power.bit1 = 0;
+		h.header_data.power.bit0 = 0;
+		break;
+
+	case AM7XXX_POWER_LOW:
+		h.header_data.power.bit2 = 0;
+		h.header_data.power.bit1 = 0;
+		h.header_data.power.bit0 = 1;
+		break;
+
+	case AM7XXX_POWER_MIDDLE:
+		h.header_data.power.bit2 = 0;
+		h.header_data.power.bit1 = 1;
+		h.header_data.power.bit0 = 0;
+		break;
+
+	case AM7XXX_POWER_HIGH:
+		h.header_data.power.bit2 = 0;
+		h.header_data.power.bit1 = 1;
+		h.header_data.power.bit0 = 1;
+		break;
+
+	case AM7XXX_POWER_TURBO:
+		h.header_data.power.bit2 = 1;
+		h.header_data.power.bit1 = 0;
+		h.header_data.power.bit0 = 0;
+		break;
+
+	default:
+		error(dev->ctx, "Unsupported power mode.\n");
+		return -EINVAL;
+	};
+
+	ret = send_header(dev, &h);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int picopix_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
+{
+	switch(power) {
+	case AM7XXX_POWER_LOW:
+		return send_command(dev, AM7XXX_PACKET_TYPE_PICOPIX_POWER_LOW);
+
+	case AM7XXX_POWER_MIDDLE:
+		return send_command(dev, AM7XXX_PACKET_TYPE_PICOPIX_POWER_MEDIUM);
+
+	case AM7XXX_POWER_HIGH:
+		return send_command(dev, AM7XXX_PACKET_TYPE_PICOPIX_POWER_HIGH);
+
+	case AM7XXX_POWER_OFF:
+	case AM7XXX_POWER_TURBO:
+	default:
+		error(dev->ctx, "Unsupported power mode.\n");
+		return -EINVAL;
+	};
+}
+
+static int default_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom)
+{
+	int ret;
+	struct am7xxx_header h = {
+		.packet_type     = AM7XXX_PACKET_TYPE_ZOOM,
+		.direction       = AM7XXX_DIRECTION_OUT,
+		.header_data_len = sizeof(struct am7xxx_zoom_header),
+		.unknown2        = 0x3e,
+		.unknown3        = 0x10,
+	};
+
+	switch(zoom) {
+	case AM7XXX_ZOOM_ORIGINAL:
+		h.header_data.zoom.bit1 = 0;
+		h.header_data.zoom.bit0 = 0;
+		break;
+
+	case AM7XXX_ZOOM_H:
+		h.header_data.zoom.bit1 = 0;
+		h.header_data.zoom.bit0 = 1;
+		break;
+
+	case AM7XXX_ZOOM_H_V:
+		h.header_data.zoom.bit1 = 1;
+		h.header_data.zoom.bit0 = 0;
+		break;
+
+	case AM7XXX_ZOOM_TEST:
+		h.header_data.zoom.bit1 = 1;
+		h.header_data.zoom.bit0 = 1;
+		break;
+
+	case AM7XXX_ZOOM_TELE:
+	default:
+		error(dev->ctx, "Unsupported zoom mode.\n");
+		return -EINVAL;
+	};
+
+	ret = send_header(dev, &h);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int picopix_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom)
+{
+	int ret;
+	am7xxx_packet_type packet_type;
+
+	switch(zoom) {
+	case AM7XXX_ZOOM_ORIGINAL:
+		packet_type = AM7XXX_PACKET_TYPE_PICOPIX_DISABLE_TI;
+		break;
+
+	case AM7XXX_ZOOM_TELE:
+		packet_type = AM7XXX_PACKET_TYPE_PICOPIX_ENABLE_TI;
+		break;
+
+	case AM7XXX_ZOOM_H:
+	case AM7XXX_ZOOM_H_V:
+	case AM7XXX_ZOOM_TEST:
+	default:
+		error(dev->ctx, "Unsupported zoom mode.\n");
+		return -EINVAL;
+	};
+
+	ret = send_command(dev, packet_type);
+	if (ret < 0)
+		return ret;
+
+	/* The Windows drivers wait for 100ms and send the same command again,
+	 * probably to overcome a firmware deficiency */
+	ret = msleep(100);
+	if (ret < 0)
+		return ret;
+
+	return send_command(dev, packet_type);
+}
+
 /* Public API */
 
 AM7XXX_PUBLIC int am7xxx_init(am7xxx_context **ctx)
 {
-	int ret = 0;
+	int ret;
 
 	*ctx = malloc(sizeof(**ctx));
 	if (*ctx == NULL) {
@@ -662,10 +1101,12 @@ AM7XXX_PUBLIC int am7xxx_init(am7xxx_context **ctx)
 	(*ctx)->log_level = AM7XXX_LOG_TRACE;
 
 	ret = libusb_init(&((*ctx)->usb_context));
-	if (ret < 0)
+	if (ret < 0) {
+		error(*ctx, "libusb_init failed: %s\n", libusb_error_name(ret));
 		goto out_free_context;
+	}
 
-	libusb_set_debug((*ctx)->usb_context, 3);
+	libusb_set_debug((*ctx)->usb_context, LIBUSB_LOG_LEVEL_INFO);
 
 	ret = scan_devices(*ctx, SCAN_OP_BUILD_DEVLIST , 0, NULL);
 	if (ret < 0) {
@@ -758,7 +1199,8 @@ AM7XXX_PUBLIC int am7xxx_close_device(am7xxx_device *dev)
 		return -EINVAL;
 	}
 	if (dev->usb_device) {
-		libusb_release_interface(dev->usb_device, 0);
+		wait_for_trasfer_completed(dev);
+		libusb_release_interface(dev->usb_device, dev->desc->interface_number);
 		libusb_close(dev->usb_device);
 		dev->usb_device = NULL;
 	}
@@ -769,31 +1211,18 @@ AM7XXX_PUBLIC int am7xxx_get_device_info(am7xxx_device *dev,
 			   am7xxx_device_info *device_info)
 {
 	int ret;
-	struct am7xxx_header h = {
-		.packet_type     = AM7XXX_PACKET_TYPE_DEVINFO,
-		.direction       = AM7XXX_DIRECTION_OUT,
-		.header_data_len = 0x00,
-		.unknown2        = 0x3e,
-		.unknown3        = 0x10,
-		.header_data = {
-			.devinfo = {
-				.native_width  = 0,
-				.native_height = 0,
-				.unknown0      = 0,
-				.unknown1      = 0,
-			},
-		},
-	};
+	struct am7xxx_header h;
 
 	if (dev->device_info) {
 		memcpy(device_info, dev->device_info, sizeof(*device_info));
 		return 0;
 	}
 
-	ret = send_header(dev, &h);
+	ret = send_command(dev, AM7XXX_PACKET_TYPE_DEVINFO);
 	if (ret < 0)
 		return ret;
 
+	memset(&h, 0, sizeof(h));
 	ret = read_header(dev, &h);
 	if (ret < 0)
 		return ret;
@@ -922,100 +1351,60 @@ AM7XXX_PUBLIC int am7xxx_send_image(am7xxx_device *dev,
 	return send_data(dev, image, image_size);
 }
 
-AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
+AM7XXX_PUBLIC int am7xxx_send_image_async(am7xxx_device *dev,
+					  am7xxx_image_format format,
+					  unsigned int width,
+					  unsigned int height,
+					  uint8_t *image,
+					  unsigned int image_size)
 {
 	int ret;
 	struct am7xxx_header h = {
-		.packet_type     = AM7XXX_PACKET_TYPE_POWER,
+		.packet_type     = AM7XXX_PACKET_TYPE_IMAGE,
 		.direction       = AM7XXX_DIRECTION_OUT,
-		.header_data_len = sizeof(struct am7xxx_power_header),
+		.header_data_len = sizeof(struct am7xxx_image_header),
 		.unknown2        = 0x3e,
 		.unknown3        = 0x10,
-	};
-
-	switch(power) {
-	case AM7XXX_POWER_OFF:
-		h.header_data.power.bit2 = 0;
-		h.header_data.power.bit1 = 0;
-		h.header_data.power.bit0 = 0;
-		break;
-
-	case AM7XXX_POWER_LOW:
-		h.header_data.power.bit2 = 0;
-		h.header_data.power.bit1 = 0;
-		h.header_data.power.bit0 = 1;
-		break;
-
-	case AM7XXX_POWER_MIDDLE:
-		h.header_data.power.bit2 = 0;
-		h.header_data.power.bit1 = 1;
-		h.header_data.power.bit0 = 0;
-		break;
-
-	case AM7XXX_POWER_HIGH:
-		h.header_data.power.bit2 = 0;
-		h.header_data.power.bit1 = 1;
-		h.header_data.power.bit0 = 1;
-		break;
-
-	case AM7XXX_POWER_TURBO:
-		h.header_data.power.bit2 = 1;
-		h.header_data.power.bit1 = 0;
-		h.header_data.power.bit0 = 0;
-		break;
-
-	default:
-		error(dev->ctx, "Unsupported power mode.\n");
-		return -EINVAL;
+		.header_data = {
+			.image = {
+				.format     = format,
+				.width      = width,
+				.height     = height,
+				.image_size = image_size,
+			},
+		},
 	};
 
 	ret = send_header(dev, &h);
 	if (ret < 0)
 		return ret;
 
-	return 0;
+	if (image == NULL || image_size == 0) {
+		warning(dev->ctx, "Not sending any data, check the 'image' or 'image_size' parameters\n");
+		return 0;
+	}
+
+	return send_data_async(dev, image, image_size);
 }
 
-AM7XXX_PUBLIC int am7xxx_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom)
+AM7XXX_PUBLIC int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
 {
-	int ret;
-	struct am7xxx_header h = {
-		.packet_type     = AM7XXX_PACKET_TYPE_ZOOM,
-		.direction       = AM7XXX_DIRECTION_OUT,
-		.header_data_len = sizeof(struct am7xxx_zoom_header),
-		.unknown2        = 0x3e,
-		.unknown3        = 0x10,
-	};
-
-	switch(zoom) {
-	case AM7XXX_ZOOM_ORIGINAL:
-		h.header_data.zoom.bit1 = 0;
-		h.header_data.zoom.bit0 = 0;
-		break;
-
-	case AM7XXX_ZOOM_H:
-		h.header_data.zoom.bit1 = 0;
-		h.header_data.zoom.bit0 = 1;
-		break;
-
-	case AM7XXX_ZOOM_H_V:
-		h.header_data.zoom.bit1 = 1;
-		h.header_data.zoom.bit0 = 0;
-		break;
-
-	case AM7XXX_ZOOM_TEST:
-		h.header_data.zoom.bit1 = 1;
-		h.header_data.zoom.bit0 = 1;
-		break;
+	if (dev->desc->ops.set_power_mode == NULL) {
+		warning(dev->ctx,
+			"setting power mode is unsupported on this device\n");
+		return 0;
+	}
 
-	default:
-		error(dev->ctx, "Unsupported zoom mode.\n");
-		return -EINVAL;
-	};
+	return dev->desc->ops.set_power_mode(dev, power);
+}
 
-	ret = send_header(dev, &h);
-	if (ret < 0)
-		return ret;
+AM7XXX_PUBLIC int am7xxx_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom)
+{
+	if (dev->desc->ops.set_zoom_mode == NULL) {
+		warning(dev->ctx,
+			"setting zoom mode is unsupported on this device\n");
+		return 0;
+	}
 
-	return 0;
+	return dev->desc->ops.set_zoom_mode(dev, zoom);
 }