+
+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;
+
+       *ctx = malloc(sizeof(**ctx));
+       if (*ctx == NULL) {
+               fatal("cannot allocate the context (%s)\n", strerror(errno));
+               ret = -ENOMEM;
+               goto out;
+       }
+       memset(*ctx, 0, sizeof(**ctx));
+
+       /* Set the highest log level during initialization */
+       (*ctx)->log_level = AM7XXX_LOG_TRACE;
+
+       ret = libusb_init(&((*ctx)->usb_context));
+       if (ret < 0)
+               goto out_free_context;
+
+       libusb_set_debug((*ctx)->usb_context, LIBUSB_LOG_LEVEL_INFO);
+
+       ret = scan_devices(*ctx, SCAN_OP_BUILD_DEVLIST , 0, NULL);
+       if (ret < 0) {
+               error(*ctx, "scan_devices() failed\n");
+               am7xxx_shutdown(*ctx);
+               goto out;
+       }
+
+       /* Set a quieter log level as default for normal operation */
+       (*ctx)->log_level = AM7XXX_LOG_ERROR;
+       return 0;
+
+out_free_context:
+       free(*ctx);
+       *ctx = NULL;
+out:
+       return ret;
+}
+
+AM7XXX_PUBLIC void am7xxx_shutdown(am7xxx_context *ctx)
+{
+       am7xxx_device *current;
+
+       if (ctx == NULL) {
+               fatal("context must not be NULL!\n");
+               return;
+       }
+
+       current = ctx->devices_list;
+       while (current) {
+               am7xxx_device *next = current->next;
+               am7xxx_close_device(current);
+               free(current->device_info);
+               free(current);
+               current = next;
+       }
+
+       libusb_exit(ctx->usb_context);
+       free(ctx);
+       ctx = NULL;
+}
+
+AM7XXX_PUBLIC void am7xxx_set_log_level(am7xxx_context *ctx, am7xxx_log_level log_level)
+{
+       ctx->log_level = log_level;
+}
+
+AM7XXX_PUBLIC int am7xxx_open_device(am7xxx_context *ctx, am7xxx_device **dev,
+                      unsigned int device_index)
+{
+       int ret;
+
+       if (ctx == NULL) {
+               fatal("context must not be NULL!\n");
+               return -EINVAL;
+       }
+
+       ret = scan_devices(ctx, SCAN_OP_OPEN_DEVICE, device_index, dev);
+       if (ret < 0) {
+               errno = ENODEV;
+               goto out;
+       } else if (ret > 0) {
+               warning(ctx, "device %d already open\n", device_index);
+               errno = EBUSY;
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* Philips/Sagemcom PicoPix projectors require that the DEVINFO packet
+        * is the first one to be sent to the device in order for it to
+        * successfully return the correct device information.
+        *
+        * So, if there is not a cached version of it (from a previous open),
+        * we ask for device info at open time,
+        */
+       if ((*dev)->device_info == NULL) {
+               ret = am7xxx_get_device_info(*dev, NULL);
+               if (ret < 0)
+                       error(ctx, "cannot get device info\n");
+       }
+
+out:
+       return ret;
+}
+
+AM7XXX_PUBLIC int am7xxx_close_device(am7xxx_device *dev)
+{
+       if (dev == NULL) {
+               fatal("dev must not be NULL!\n");
+               return -EINVAL;
+       }
+       if (dev->usb_device) {
+               wait_for_trasfer_completed(dev);
+               libusb_release_interface(dev->usb_device, dev->desc->interface_number);
+               libusb_close(dev->usb_device);
+               dev->usb_device = NULL;
+       }
+       return 0;
+}
+
+AM7XXX_PUBLIC int am7xxx_get_device_info(am7xxx_device *dev,
+                          am7xxx_device_info *device_info)
+{
+       int ret;
+       struct am7xxx_header h;
+
+       if (dev->device_info) {
+               memcpy(device_info, dev->device_info, sizeof(*device_info));
+               return 0;
+       }
+
+       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;
+
+       if (h.packet_type != AM7XXX_PACKET_TYPE_DEVINFO) {
+               error(dev->ctx, "expected packet type: %d, got %d instead!\n",
+                     AM7XXX_PACKET_TYPE_DEVINFO, h.packet_type);
+               errno = ENOTSUP;
+               return -ENOTSUP;
+       }
+
+       dev->device_info = malloc(sizeof(*dev->device_info));
+       if (dev->device_info == NULL) {
+               error(dev->ctx, "cannot allocate a device info (%s)\n",
+                      strerror(errno));
+               return -ENOMEM;
+       }
+       memset(dev->device_info, 0, sizeof(*dev->device_info));
+
+       dev->device_info->native_width = h.header_data.devinfo.native_width;
+       dev->device_info->native_height = h.header_data.devinfo.native_height;
+#if 0
+       /* No reason to expose these in the public API until we know what they mean */
+       dev->device_info->unknown0 = h.header_data.devinfo.unknown0;
+       dev->device_info->unknown1 = h.header_data.devinfo.unknown1;
+#endif
+
+       return 0;
+}
+
+AM7XXX_PUBLIC int am7xxx_calc_scaled_image_dimensions(am7xxx_device *dev,
+                                       unsigned int upscale,
+                                       unsigned int original_width,
+                                       unsigned int original_height,
+                                       unsigned int *scaled_width,
+                                       unsigned int *scaled_height)
+{
+
+       am7xxx_device_info device_info;
+       float width_ratio;
+       float height_ratio;
+       int ret;
+
+       ret = am7xxx_get_device_info(dev, &device_info);
+       if (ret < 0) {
+               error(dev->ctx, "cannot get device info\n");
+               return ret;
+       }
+
+       /*
+        * Check if we need to rescale; if the input image fits the native
+        * dimensions there is no need to, unless we want to upscale.
+        */
+       if (!upscale &&
+           original_width <= device_info.native_width &&
+           original_height <= device_info.native_height ) {
+               debug(dev->ctx, "CASE 0, no rescaling, the original image fits already\n");
+               *scaled_width = original_width;
+               *scaled_height = original_height;
+               return 0;
+       }
+
+       /* Input dimensions relative to the device native dimensions */
+       width_ratio =  (float)original_width / device_info.native_width;
+       height_ratio = (float)original_height / device_info.native_height;
+
+       if (width_ratio > height_ratio) {
+               /*
+                * The input is proportionally "wider" than the device viewport
+                * so its height needs to be adjusted
+                */
+               debug(dev->ctx, "CASE 1, original image wider, adjust the scaled height\n");
+               *scaled_width = device_info.native_width;
+               *scaled_height = (unsigned int)lroundf(original_height / width_ratio);
+       } else if (width_ratio < height_ratio) {
+               /*
+                * The input is proportionally "taller" than the device viewport
+                * so its width needs to be adjusted
+                */
+               debug(dev->ctx, "CASE 2 original image taller, adjust the scaled width\n");
+               *scaled_width = (unsigned int)lroundf(original_width / height_ratio);
+               *scaled_height = device_info.native_height;
+       } else {
+               debug(dev->ctx, "CASE 3, just rescale, same aspect ratio already\n");
+               *scaled_width = device_info.native_width;
+               *scaled_height = device_info.native_height;
+       }
+       debug(dev->ctx, "scaled dimensions: %dx%d\n", *scaled_width, *scaled_height);
+
+       return 0;
+}
+
+AM7XXX_PUBLIC int am7xxx_send_image(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_IMAGE,
+               .direction       = AM7XXX_DIRECTION_OUT,
+               .header_data_len = sizeof(struct am7xxx_image_header),
+               .unknown2        = 0x3e,
+               .unknown3        = 0x10,
+               .header_data = {
+                       .image = {
+                               .format     = format,
+                               .width      = width,
+                               .height     = height,
+                               .image_size = image_size,
+                       },
+               },
+       };
+
+       ret = send_header(dev, &h);
+       if (ret < 0)
+               return ret;
+
+       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(dev, image, image_size);
+}
+
+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_IMAGE,
+               .direction       = AM7XXX_DIRECTION_OUT,
+               .header_data_len = sizeof(struct am7xxx_image_header),
+               .unknown2        = 0x3e,
+               .unknown3        = 0x10,
+               .header_data = {
+                       .image = {
+                               .format     = format,
+                               .width      = width,
+                               .height     = height,
+                               .image_size = image_size,
+                       },
+               },
+       };
+
+       ret = send_header(dev, &h);
+       if (ret < 0)
+               return ret;
+
+       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_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
+{
+       if (dev->desc->ops.set_power_mode == NULL) {
+               warning(dev->ctx,
+                       "setting power mode is unsupported on this device\n");
+               return 0;
+       }
+
+       return dev->desc->ops.set_power_mode(dev, power);
+}
+
+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 dev->desc->ops.set_zoom_mode(dev, zoom);
+}