+
+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) {
+ error(*ctx, "libusb_init failed: %s\n", libusb_error_name(ret));
+ 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);
+}