/****************************************************************/
/* spca50x_sdram.c - Gphoto2 library for cameras with sunplus   */
/*             spca50x chips                                    */
/*                                                              */
/* Copyright 2002, 2003 Till Adam                               */
/*                                                              */
/* Author: Till Adam <till@adam-lilienthal.de>                  */
/*                                                              */
/* This library is free software; you can redistribute it       */
/* and/or modify it under the terms of the GNU Library General  */
/* Public License as published by the Free Software Foundation; */
/* either version 2 of the License, or (at your option) any     */
/* later version.                                               */
/*                                                              */
/* This library is distributed in the hope that it will be      */
/* useful, but WITHOUT ANY WARRANTY; without even the implied   */
/* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      */
/* PURPOSE.  See the GNU Library General Public License for     */
/* more details.                                                */
/*                                                              */
/* You should have received a copy of the GNU Library General   */
/* Public License along with this library; if not, write to the */
/* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,*/
/* Boston, MA  02110-1301  USA					*/
/****************************************************************/
#define _DEFAULT_SOURCE

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <gphoto2/gphoto2.h>
#include "libgphoto2/gphoto2-endian.h"

#include "libgphoto2/i18n.h"

#include "spca50x.h"
#include "spca50x-sdram.h"
#include "spca50x-registers.h"
#include "spca50x-avi-header.h"


#define GP_MODULE "spca50x"

static int spca50x_mode_set_idle (CameraPrivateLibrary * lib);
static int spca50x_is_idle (CameraPrivateLibrary * lib);
static int spca50x_mode_set_download (CameraPrivateLibrary * lib);
static int spca50x_download_data (CameraPrivateLibrary * lib, uint32_t start,
				 unsigned int size, uint8_t * buf);
static int spca50x_get_FATs (CameraPrivateLibrary * lib, int dramtype);
static int spca50x_sdram_get_file_count_and_fat_count (
		CameraPrivateLibrary * lib, int dramtype);
static int spca50x_get_avi_thumbnail (CameraPrivateLibrary * lib,
				     uint8_t ** buf, unsigned int *len,
				     struct SPCA50xFile *g_file);
static int spca50x_get_image_thumbnail (CameraPrivateLibrary * lib,
				       uint8_t ** buf, unsigned int *len,
				       struct SPCA50xFile *g_file);
static int spca50x_get_avi (CameraPrivateLibrary * lib, uint8_t ** buf,
			   unsigned int *len, struct SPCA50xFile *g_file);
static int spca50x_get_image (CameraPrivateLibrary * lib, uint8_t ** buf,
			     unsigned int *len, struct SPCA50xFile *g_file);
static inline uint8_t *put_dword (uint8_t * ptr, uint32_t value);


static int
spca50x_sdram_get_fat_page (CameraPrivateLibrary * lib, int index,
			    int dramtype, uint8_t *p)
{
	switch (dramtype) {
		case 4:	/* 128 Mbit */
			CHECK (spca50x_download_data
					(lib, 0x7fff80 -
					 index * 0x80,
					 SPCA50X_FAT_PAGE_SIZE, p));
			break;
		case 3:	/* 64 Mbit */
			CHECK (spca50x_download_data
					(lib, 0x3fff80 -
					 index * 0x80,
					 SPCA50X_FAT_PAGE_SIZE, p));
			break;
		default:
			gp_log(GP_LOG_ERROR, "spca50x", "spca50x_sdram_get_fat_page: dramtype %d unhandled", dramtype);
			return GP_ERROR;
	}

	return GP_OK;
}

static int
spca50x_sdram_get_file_count_and_fat_count (CameraPrivateLibrary * lib,
					    int dramtype)
{
	uint8_t theFat[256];

	lib->num_fats = 0;
	lib->num_files_on_sdram = 0;

	if (lib->bridge == BRIDGE_SPCA500){
		uint8_t lower, upper;

		CHECK (gp_port_usb_msg_write (lib->gpdev, 0x5, 0, 0, NULL, 0));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
		sleep (1);
#endif
		CHECK (gp_port_usb_msg_read
				(lib->gpdev, 0, 0, 0xe15,
				 (char *) & lib->num_files_on_sdram, 1));
		le32toh (lib->num_files_on_sdram);

		/*  get fatscount */
		CHECK (gp_port_usb_msg_write
				(lib->gpdev, 0x05, 0x0000, 0x0008, NULL, 0));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
		sleep (1);
#endif
		CHECK (gp_port_usb_msg_read
				(lib->gpdev, 0, 0, 0x0e19,
				 (char *)&lower, 1));
		CHECK (gp_port_usb_msg_read
				(lib->gpdev, 0, 0, 0x0e20,
				 (char *)&upper, 1));

		lib->num_fats = (((upper & 0xFF) << 8) | (lower & 0xFF));
	} else {
		while (1) {
			CHECK (spca50x_sdram_get_fat_page (lib, lib->num_fats,
						    dramtype, theFat));
			if (theFat[0] == 0xFF)
				break;

			if (theFat[0] == 0x08 || theFat[0] == 0x00)
				lib->num_files_on_sdram++;

			lib->num_fats++;
		}
	}
	return (GP_OK);
}

int
spca50x_sdram_delete_file (CameraPrivateLibrary * lib, unsigned int index)
{
	struct SPCA50xFile *g_file;
	uint16_t fat_index;

	CHECK (spca50x_sdram_get_file_info (lib, index, &g_file));

	if (lib->bridge == BRIDGE_SPCA500)
		fat_index = 0x70FF - g_file->fat_start - 1;
	else			/* if (lib->bridge == BRIDGE_SPCA504) */
		fat_index = 0xD8000 - g_file->fat_start - 1;

	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0x06, fat_index, 0x0007, NULL, 0));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
	sleep (1);
#endif

	/* Reread fats the next time it is accessed */
	lib->dirty_sdram = 1;

	return GP_OK;
}

int
spca50x_sdram_delete_all (CameraPrivateLibrary * lib)
{
	if (lib->fw_rev == 2) {
		CHECK (gp_port_usb_msg_write
		       (lib->gpdev, 0x71, 0x0000, 0x0000, NULL, 0));

	} else {
		CHECK (gp_port_usb_msg_write
		       (lib->gpdev, 0x02, 0x0000, 0x0005, NULL, 0));
	}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
	sleep (3);
#endif

	/* Reread fats the next time it is accessed */
	lib->dirty_sdram = 1;
	return GP_OK;
}

int
spca50x_sdram_request_file (CameraPrivateLibrary * lib, uint8_t ** buf,
		     unsigned int *len, unsigned int number, int *type)
{
	struct SPCA50xFile *g_file;

	CHECK (spca50x_sdram_get_file_info (lib, number, &g_file));

	*type = g_file->mime_type;
	if (g_file->mime_type == SPCA50X_FILE_TYPE_AVI)
		return (spca50x_get_avi (lib, buf, len, g_file));
	else
		return (spca50x_get_image (lib, buf, len, g_file));
}

static int
spca50x_get_image (CameraPrivateLibrary * lib, uint8_t ** buf,
		  unsigned int *len, struct SPCA50xFile *g_file)
{
	uint8_t *p, *lp_jpg;
	uint8_t qIndex = 0, format;
	uint32_t start;
	uint8_t *mybuf;
	int size, o_size, file_size, ret;
	int omit_escape = 0;

	p = g_file->fat;
	if (!p) return GP_ERROR;

	/* get the position in memory where the image is */
	start = (p[1] & 0xff) + (p[2] & 0xff) * 0x100;
	start *= 128;

	/* decode the image size */
	if (lib->bridge == BRIDGE_SPCA500) {
		o_size = size = (p[5] & 0xff) * 0x100 + (p[6] & 0xff) * 0x10000;
		qIndex = p[7] & 0x0f;
	} else {
		o_size = size =
				(p[13] & 0xff) * 0x10000
				+ (p[12] & 0xff) * 0x100
				+ (p[11] & 0xff);
		if (lib->fw_rev == 1) {
			qIndex = p[7] & 0x0f;
		} else if (lib->fw_rev == 2) {
			omit_escape = 1;
			qIndex = p[10] & 0x0f;
		}
	}

	format = 0x21;

	/* align */
	if (size % 64 != 0)
		size = ((size / 64) + 1) * 64;

	file_size = size + SPCA50X_JPG_DEFAULT_HEADER_LENGTH + 1024 * 10;

	/* slurp in the image */
	mybuf = malloc (size);
	if (!mybuf)
		return GP_ERROR_NO_MEMORY;

	if (lib->bridge == BRIDGE_SPCA504) {
		ret = spca50x_download_data (lib, start, size, mybuf);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
	} else if (lib->bridge == BRIDGE_SPCA500) {
		/* find the file index on the camera */
		int index;

		index = (g_file->fat - lib->fats) / SPCA50X_FAT_PAGE_SIZE;
		spca50x_reset (lib);
		/* trigger upload of image at that index */
		ret = gp_port_usb_msg_write (lib->gpdev, 0x06,
					      0x70FF - index, 0x01, NULL, 0);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
		sleep (1);
#endif
		ret = gp_port_read (lib->gpdev, (char *)mybuf, size);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
		/* the smallest ones are in a different format */
		if ((p[20] & 0xff) == 2)
			format = 0x22;
	}
	/* now build a jpeg */
	lp_jpg = malloc (file_size);
	if (!lp_jpg) {
		free (mybuf);
		return GP_ERROR_NO_MEMORY;
	}
	create_jpeg_from_data (lp_jpg, mybuf, qIndex, g_file->width,
			       g_file->height, format, o_size, &file_size,
			       0, omit_escape);

	free (mybuf);
	lp_jpg = realloc (lp_jpg, file_size);
	*buf = lp_jpg;
	*len = file_size;

	return (GP_OK);
}

static int
spca50x_get_avi (CameraPrivateLibrary * lib, uint8_t ** buf,
		unsigned int *len, struct SPCA50xFile *g_file)
{
	int i, j, length, ret;
	int frame_count = 0, frames_per_fat = 0, fn = 0;
	unsigned int size = 0;
	int file_size;
	int index_size;
	uint32_t frame_size = 0, frame_width = 0, frame_height = 0;
	uint32_t total_frame_size = 0;
	uint32_t start = 0;
	uint8_t *p, *mybuf, *avi, *start_of_file, *start_of_frame, *data;
	uint8_t qIndex;
	uint8_t *avi_index, *avi_index_ptr;
	uint8_t index_item[16];

	/* FIXME */
	if (lib->bridge == BRIDGE_SPCA500)
		return GP_ERROR_NOT_SUPPORTED;

	p = g_file->fat;

	if (lib->fw_rev == 2)
		qIndex = p[10] & 0x0f;
	else
		qIndex = p[7] & 0x0f;

	avi = mybuf = start_of_file = data = NULL;

	/* get the position in memory where the movie is */
	start = (p[1] & 0xff) + (p[2] & 0xff) * 0x100;
	start *= 128;

	/* Frame w and h and qIndex dont change, so just use the values
	 * of the first frame */
	frame_width = (p[8] & 0xFF) * 16;
	frame_height = (p[9] & 0xFF) * 16;

	/* Each movie consists of a number of frames, and can have more than
	 * one fat page. Iterate over all fat pages for this movie and count
	 * all frames in all fats. Same for size. */
	for (i = g_file->fat_start; i <= g_file->fat_end; i++) {
		frames_per_fat = (p[49] & 0xFF) * 0x100 + (p[48] & 0xFF);
		frame_count += frames_per_fat;
		size += (p[13] & 0xFF) * 0x10000 + (p[12] & 0xFF) * 0x100 +
			(p[11] & 0xFF);
		if (frames_per_fat < 60)
			break;
		p += SPCA50X_FAT_PAGE_SIZE;
	}
	/* align */
	size = (size + 63) & 0xffffffc0;

	/* mem for index */
	index_size = frame_count * 16;
	avi_index_ptr = avi_index = malloc (index_size);
	if (!avi_index)
		return GP_ERROR_NO_MEMORY;

	/* slurp in the movie */
	mybuf = malloc (size);
	if (!mybuf) {
		free (avi_index);
		return GP_ERROR_NO_MEMORY;
	}

	ret = spca50x_download_data (lib, start, size, mybuf);
	if (ret < GP_OK) {
		free (avi_index);
		free (mybuf);
		return ret;
	}

	/* Now write our data to a file with an avi header */
	file_size = size + SPCA50X_AVI_HEADER_LENGTH
		+ (SPCA50X_JPG_DEFAULT_HEADER_LENGTH * frame_count)
		+ 8 + index_size	/* for index chunk */
		+ 1024 * 10 * frame_count;

	GP_DEBUG("file_size = %d", file_size);
	avi = malloc (file_size);
	if (!avi) {
		free (avi_index);
		free (mybuf);
		return GP_ERROR_NO_MEMORY;
	}

	start_of_file = avi;

	/* prepare index item */
	put_dword (index_item, 0x63643030);	/* 00dc */
	put_dword (index_item + 4, 0x10);	/*  KEYFRAME */

	/* copy the header from the template */
	memcpy (avi, SPCA50xAviHeader, SPCA50X_AVI_HEADER_LENGTH);

	/* put the width and height into the riff header */
	put_dword(avi + 0x40, frame_width);
	put_dword(avi + 0x44, frame_height);

	/* and at 0xb0 and 0xb4 */
	put_dword(avi + 0xb0, frame_width);
	put_dword(avi + 0xb4, frame_height);

	avi += SPCA50X_AVI_HEADER_LENGTH;

	/* Reset to the first fat */
	p = g_file->fat;
	data = mybuf;

	/* Iterate over fats and frames in each fat and build a jpeg for
	 * each frame. Add the jpeg to the avi and write an index entry. */
	for (i = g_file->fat_start; i <= g_file->fat_end; i++) {
		frames_per_fat = ((p[49] & 0xFF) * 0x100 + (p[48] & 0xFF));

		/* frames per fat might be lying, so double check how
		   many frames were already processed */
		if (frames_per_fat > 60 || frames_per_fat == 0
		    || fn + frames_per_fat > frame_count)
			break;

		for (j = 0; j < frames_per_fat; j++) {
			frame_size = ((p[52 + j * 3] & 0xFF) * 0x10000)
				+ ((p[51 + j * 3] & 0xFF) * 0x100) +
				(p[50 + j * 3] & 0xFF);

			GP_DEBUG("frame_size = %d", frame_size);

			if ((file_size - (avi - start_of_file)) < SPCA50X_AVI_FRAME_HEADER_LENGTH) {
				free (mybuf);
				GP_DEBUG("BAD: writing more than we allocated (%ld, %ld vs total %d)", (avi-start_of_file), (file_size - (avi - start_of_file)), SPCA50X_AVI_FRAME_HEADER_LENGTH);
				return GP_ERROR_CORRUPTED_DATA;
			}

			memcpy (avi, SPCA50xAviFrameHeader,
					SPCA50X_AVI_FRAME_HEADER_LENGTH);

			avi += SPCA50X_AVI_FRAME_HEADER_LENGTH;
			start_of_frame = avi;

			/* jpeg starts here */
			if ((data - mybuf) + frame_size > size) {
				GP_DEBUG("BAD: accessing more than we read (%u vs total %d)", (unsigned int)((data-mybuf)+frame_size), size);
				free (mybuf);
				return GP_ERROR_CORRUPTED_DATA;
			}
			ret = create_jpeg_from_data (avi, data, qIndex, frame_width,
					       frame_height, 0x22, frame_size,
					       &length, 1, 0);

			if (ret != GP_OK) {
				free (mybuf);
				return ret;
			}

			GP_DEBUG("avi added length = %d, width %d , height %d", length, frame_width, frame_height);
			data += (frame_size + 7) & 0xfffffff8;
			avi += length;
			/* Make sure the next frame is aligned */
			if ((avi - start_of_frame) % 2 != 0)
				avi++;

			/* Now we know the real frame size after escaping and
			 * adding the EOI marker. Put it in the frame header. */
			frame_size = avi - start_of_frame;
			put_dword (start_of_frame - 4, frame_size);
			put_dword (index_item + 8,
				   start_of_frame - start_of_file -
				   SPCA50X_AVI_HEADER_LENGTH - 4);
			put_dword (index_item + 12, frame_size);
			memcpy (avi_index_ptr, index_item, 16);
			avi_index_ptr += 16;
			fn++;
		}
		p += SPCA50X_FAT_PAGE_SIZE;
	}
	total_frame_size = avi - (start_of_file + SPCA50X_AVI_HEADER_LENGTH - 4);
	put_dword (start_of_file + SPCA50X_AVI_HEADER_LENGTH - 8,
		   total_frame_size);

	avi = put_dword (avi, 0x31786469);	/* idx1 */
	avi = put_dword (avi, index_size);
	memcpy (avi, avi_index, index_size);
	avi += index_size;
	free (avi_index);
	put_dword (start_of_file + 0x30, frame_count);
	put_dword (start_of_file + 0x8c, frame_count);

	/* And reuse total_frame_size for the file size RIFF marker at offset
	 * 4 */
	total_frame_size = avi - (start_of_file + 4);
	put_dword (start_of_file + 4, total_frame_size);

	free (mybuf);
	*len = avi - start_of_file;
	start_of_file = realloc (start_of_file, avi - start_of_file);
	*buf = start_of_file;
	return (GP_OK);
}

int
spca50x_sdram_request_thumbnail (CameraPrivateLibrary * lib, uint8_t ** buf,
			  unsigned int *len, unsigned int number, int *type)
{
	struct SPCA50xFile *g_file;

	CHECK (spca50x_sdram_get_file_info (lib, number, &g_file));

	*type = g_file->mime_type;
	if (g_file->mime_type == SPCA50X_FILE_TYPE_AVI) {
		return (spca50x_get_avi_thumbnail (lib, buf, len, g_file));
	} else {

		/* the spca500 stores the quality in p[20], the rest of them
		 * in [p40].  We need to check for the smallest resolution of
		 * the mini, since resolution does not have thumbnails.
		 * Download the full image instead.
		 * Low:    320x240                2
		 * Middle: 640x480                0
		 * High:  1024x768 (interpolated) 1*/
		if (lib->bridge == BRIDGE_SPCA500
		    && (g_file->fat[20] & 0xFF) == 2) {
			return (spca50x_get_image (lib, buf, len, g_file));
		} else {
			return (spca50x_get_image_thumbnail
				(lib, buf, len, g_file));
		}
	}
}


static int
spca50x_get_avi_thumbnail (CameraPrivateLibrary * lib, uint8_t ** buf,
			  unsigned int *len, struct SPCA50xFile *g_file)
{
	uint8_t *p, *lp_jpg;
	uint8_t qIndex;
	uint32_t start;
	uint8_t *mybuf;
	int size, o_size, file_size;
	int ret;

	/* FIXME */
	if (lib->bridge == BRIDGE_SPCA500)
		return GP_ERROR_NOT_SUPPORTED;

	p = g_file->fat;

	/* get the position in memory where the image is */
	start = (p[1] & 0xff) + (p[2] & 0xff) * 0x100;
	start *= 128;

	/* decode the image size */
	o_size = size =
		(p[52] & 0xff) * 0x10000 + (p[51] & 0xff) * 0x100 +
		(p[50] & 0xff);
	qIndex = p[7] & 0x0f;

	/* align */
	if (size % 64 != 0)
		size = ((size / 64) + 1) * 64;

	file_size = size + SPCA50X_JPG_DEFAULT_HEADER_LENGTH + 1024 * 10;

	/* slurp in the image */
	mybuf = malloc (size);
	if (!mybuf)
		return GP_ERROR_NO_MEMORY;

	ret = spca50x_download_data (lib, start, size, mybuf);
	if (ret < GP_OK) {
		free (mybuf);
		return ret;
	}

	/* now build a jpeg */
	lp_jpg = malloc (file_size);
	if (!lp_jpg) {
		free (mybuf);
		return GP_ERROR_NO_MEMORY;
	}

	create_jpeg_from_data (lp_jpg, mybuf, qIndex, g_file->width,
			       g_file->height, 0x22, o_size, &file_size, 0, 0);
	free (mybuf);
	lp_jpg = realloc (lp_jpg, file_size);
	*buf = lp_jpg;
	*len = file_size;

	return (GP_OK);
}


static int
spca50x_get_image_thumbnail (CameraPrivateLibrary * lib, uint8_t ** buf,
			    unsigned int *len, struct SPCA50xFile *g_file)
{
	unsigned int size;
	uint8_t *p;
	uint32_t start;
	uint8_t *mybuf = NULL;
	uint8_t *tmp;
	unsigned int t_width, t_height;
	uint8_t *yuv_p;
	uint8_t *rgb_p;
	int headerlength, ret;

	p = g_file->fat;

	start = (p[3] & 0xff) + (p[4] & 0xff) * 0x100;
	start *= 128;

	size = g_file->width * g_file->height * 2 / 64;
	t_width = g_file->width / 8;
	t_height = g_file->height / 8;
	/* Adjust the headerlenght */
	headerlength = 13;
	if (t_width > 99) headerlength++;
	if (t_height > 99) headerlength++;

	/* align */
	if (size % 64 != 0)
		size = ((size / 64) + 1) * 64;

	mybuf = malloc (size);

	if (lib->bridge == BRIDGE_SPCA504) {
		ret = spca50x_download_data (lib, start, size, mybuf);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
	} else if (lib->bridge == BRIDGE_SPCA500) {
		/* find the file index on the camera */
		int index;

		index = (g_file->fat - lib->fats) / SPCA50X_FAT_PAGE_SIZE;
		spca50x_reset (lib);
		/* trigger upload of thumbnail at that index */
		ret = gp_port_usb_msg_write (lib->gpdev, 0x06,
					      0x70FF - index, 0x09, NULL, 0);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
		sleep (1);
#endif
		ret = gp_port_read (lib->gpdev, (char *)mybuf, size);
		if (ret < GP_OK) {
			free (mybuf);
			return ret;
		}
	}
	*len = t_width * t_height * 3 + headerlength;
	*buf = malloc (*len);
	if (!*buf) {
		free (mybuf);
		return (GP_ERROR_NO_MEMORY);
	}

	tmp = *buf;
	snprintf ((char*)tmp, *len, "P6 %d %d 255\n", t_width, t_height);
	tmp += headerlength;

	yuv_p = mybuf;
	rgb_p = tmp;
	while (yuv_p < mybuf + (t_width * t_height * 2)) {
		unsigned int u, v, y, y2;
		unsigned int r, g, b;

		y = yuv_p[0];
		y2 = yuv_p[1];
		u = yuv_p[2];
		v = yuv_p[3];

		CHECK (yuv2rgb (y, u, v, &r, &g, &b));
		*rgb_p++ = r;
		*rgb_p++ = g;
		*rgb_p++ = b;

		CHECK (yuv2rgb (y2, u, v, &r, &g, &b));
		*rgb_p++ = r;
		*rgb_p++ = g;
		*rgb_p++ = b;

		yuv_p += 4;
	}

	free (mybuf);
	return (GP_OK);
}


int
spca50x_sdram_get_info (CameraPrivateLibrary * lib)
{
	int index;
	uint8_t dramtype = 0;
	uint8_t *p;
	uint32_t start_page, end_page;
	uint8_t file_type;

	GP_DEBUG ("* spca50x_sdram_get_info");

	if (lib->bridge == BRIDGE_SPCA504) {
		if (!spca50x_is_idle (lib))
			spca50x_mode_set_idle (lib);

		spca50x_mode_set_download (lib);

		CHECK (gp_port_usb_msg_write
		       (lib->gpdev, 0x00, 0x0001, SPCA50X_REG_AutoPbSize, NULL,
			0));

		CHECK (gp_port_usb_msg_read
		       (lib->gpdev, 0, 0, SPCA50X_REG_DramType,
			(char *) & dramtype, 1));
		dramtype &= 0xFF;
	}

	CHECK (spca50x_sdram_get_file_count_and_fat_count (lib, dramtype));

	if (lib->num_files_on_sdram > 0) {
		CHECK (spca50x_get_FATs (lib, dramtype));

		index = lib->sdram_files[lib->num_files_on_sdram - 1].fat_end;
		if (index >= lib->num_fats) {
			gp_log(GP_LOG_ERROR, "spca50x", "%d exceeds num_fats %d", index, lib->num_fats);
			return GP_ERROR;
		}
		p = lib->fats + SPCA50X_FAT_PAGE_SIZE * index;
		/* p now points to the fat of the last image of the last file */

		file_type = p[0];
		start_page = ((p[2] & 0xFF) << 8) | (p[1] & 0xFF);
		end_page = start_page + (((p[6] & 0xFF) << 8) | (p[5] & 0xFF));
		if (file_type == SPCA50X_FILE_TYPE_IMAGE)
			end_page = end_page + 0xA0;

		lib->size_used = (end_page - 0x2800) * SPCA50X_FAT_PAGE_SIZE;
	} else
		lib->size_used = 0;

	lib->size_free =
		16 * 1024 * 1024 - 0x2800 * SPCA50X_FAT_PAGE_SIZE - lib->size_used;
	lib->dirty_sdram = 0;

	return GP_OK;
}

int
spca50x_sdram_get_file_info (CameraPrivateLibrary * lib, unsigned int index,
		      struct SPCA50xFile **g_file)
{
	if (lib->dirty_sdram)
		CHECK (spca50x_sdram_get_info (lib));
	*g_file = &(lib->sdram_files[index]);
	return GP_OK;
}
static int
spca50x_mode_set_idle (CameraPrivateLibrary * lib)
{
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, SPCA50X_CamMode_Idle, SPCA50X_REG_CamMode, NULL,
		0));
	return GP_OK;
}

static int
spca50x_is_idle (CameraPrivateLibrary * lib)
{
	int mode;

	CHECK (gp_port_usb_msg_read
	       (lib->gpdev, 0, 0, SPCA50X_REG_CamMode, (char *) & mode, 1));

	return mode == SPCA50X_CamMode_Idle ? 1 : 0;
}

static int
spca50x_mode_set_download (CameraPrivateLibrary * lib)
{
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, SPCA50X_CamMode_Upload, SPCA50X_REG_CamMode, NULL,
		0));
	return GP_OK;
}

static int
spca50x_download_data (CameraPrivateLibrary * lib, uint32_t start,
		      unsigned int size, uint8_t * buf)
{

	uint8_t foo;
	uint8_t vlcAddressL, vlcAddressM, vlcAddressH;

	if (!spca50x_is_idle (lib))
		spca50x_mode_set_idle (lib);

	spca50x_mode_set_download (lib);

	foo = size & 0xFF;
	CHECK (gp_port_usb_msg_write (lib->gpdev, 0, foo,
				      SPCA50X_REG_SdramSizeL, NULL, 0));
	foo = (size >> 8) & 0xFF;
	CHECK (gp_port_usb_msg_write (lib->gpdev, 0, foo,
				      SPCA50X_REG_SdramSizeM, NULL, 0));
	foo = (size >> 16) & 0xFF;
	CHECK (gp_port_usb_msg_write (lib->gpdev, 0, foo,
				      SPCA50X_REG_SdramSizeH, NULL, 0));

	CHECK (gp_port_usb_msg_read
	       (lib->gpdev, 0, 0, SPCA50X_REG_VlcAddressL,
		(char *) & vlcAddressL, 1));
	CHECK (gp_port_usb_msg_read
	       (lib->gpdev, 0, 0, SPCA50X_REG_VlcAddressM,
		(char *) & vlcAddressM, 1));
	CHECK (gp_port_usb_msg_read
	       (lib->gpdev, 0, 0, SPCA50X_REG_VlcAddressH,
		(char *) & vlcAddressH, 1));

	foo = start & 0xFF;
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, foo, SPCA50X_REG_VlcAddressL, NULL, 0));
	foo = (start >> 8) & 0xFF;
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, foo, SPCA50X_REG_VlcAddressM, NULL, 0));
	foo = (start >> 16) & 0xFF;
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, foo, SPCA50X_REG_VlcAddressH, NULL, 0));

	/* Set mode to ram -> usb */
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, SPCA50X_DramUsb, SPCA50X_REG_PbSrc, NULL, 0));
	/* and pull the trigger */
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, SPCA50X_TrigDramFifo, SPCA50X_REG_Trigger, NULL,
		0));

	CHECK (gp_port_read (lib->gpdev, (char *)buf, size));

	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, vlcAddressL, SPCA50X_REG_VlcAddressL, NULL, 0));
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, vlcAddressM, SPCA50X_REG_VlcAddressM, NULL, 0));
	CHECK (gp_port_usb_msg_write
	       (lib->gpdev, 0, vlcAddressH, SPCA50X_REG_VlcAddressH, NULL, 0));

	spca50x_mode_set_idle (lib);
	return GP_OK;
}

static int
spca50x_get_FATs (CameraPrivateLibrary * lib, int dramtype)
{
	uint8_t type;
	unsigned int index = 0;
	unsigned int file_index = 0;
	uint8_t *p = NULL;

	/* Reset image and movie counter */
	lib->num_images = lib->num_movies = 0;

	free (lib->fats);
	lib->fats = NULL;

	free (lib->sdram_files);
	lib->sdram_files = NULL;

	lib->fats = calloc (lib->num_fats , SPCA50X_FAT_PAGE_SIZE);
	lib->sdram_files = calloc (lib->num_files_on_sdram , sizeof (struct SPCA50xFile));

	p = lib->fats;
	if (lib->bridge == BRIDGE_SPCA504) {
		while (index < (unsigned int)lib->num_fats) {
			CHECK (spca50x_sdram_get_fat_page (lib, index,
						dramtype, p));
			if (p[0] == 0xFF)
				break;

			index++;
			p += SPCA50X_FAT_PAGE_SIZE;
		}
	} else if (lib->bridge == BRIDGE_SPCA500) {
		/* for the spca500, download the whole fat in one go. */
		spca50x_reset (lib);
		CHECK (gp_port_usb_msg_write
		       (lib->gpdev, 0x05, 0x00, 0x07, NULL, 0));
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
		sleep (1);
#endif
		CHECK (gp_port_read
		       (lib->gpdev, (char *)lib->fats,
			lib->num_fats * SPCA50X_FAT_PAGE_SIZE));
	}

	p = lib->fats;
	index = 0;

	while (index < (unsigned int)lib->num_fats) {
		if (file_index >= (unsigned int)lib->num_files_on_sdram) {
			free (lib->fats); lib->fats = NULL;
			free (lib->sdram_files); lib->sdram_files = NULL;
			return GP_ERROR;
		}

		type = p[0];
		/* While the spca504a indicates start of avi as 0x08 and cont.
		 * of avi as 0x80, the spca500 uses 0x03 for both. Each frame
		 * gets its own fat table with a sequence number at p[18]. */
		if ((type == 0x80) || (type == 0x03 && (p[18] != 0x00))) {
			/* continuation of an avi */
			if (!file_index)
				return GP_ERROR;
			lib->sdram_files[file_index - 1].fat_end = index;
		} else {
			/* its an image */
			if (type == 0x00 || type == 0x01) {
				lib->sdram_files[file_index].name =
					aprintf( "Image%03d.jpg", ++lib->num_images);
				lib->sdram_files[file_index].mime_type =
					SPCA50X_FILE_TYPE_IMAGE;
			} else if ((type == 0x08) || (type == 0x03)) {
				/* its the start of an avi */
				lib->sdram_files[file_index].name =
					aprintf ("Movie%03d.avi", ++lib->num_movies);
				lib->sdram_files[file_index].mime_type =
					SPCA50X_FILE_TYPE_AVI;
			} else {
				gp_log(GP_LOG_ERROR, "spca50x", "type %d unhandled - error", type);
				return GP_ERROR;
			}
			lib->sdram_files[file_index].fat = p;
			lib->sdram_files[file_index].fat_start = index;
			lib->sdram_files[file_index].fat_end = index;
			if (lib->bridge == BRIDGE_SPCA504) {
				lib->sdram_files[file_index].width =
					(p[8] & 0xFF) * 16;
				lib->sdram_files[file_index].height =
					(p[9] & 0xFF) * 16;
			} else if (lib->bridge == BRIDGE_SPCA500) {
				int w, h;

				if (p[20] == 2) {
					w = 320;
					h = 240;
				} else {
					w = 640;
					h = 480;
				}
				lib->sdram_files[file_index].width = w;
				lib->sdram_files[file_index].height = h;
			}
			lib->sdram_files[file_index].thumb = NULL;
			file_index++;
		}
		p += SPCA50X_FAT_PAGE_SIZE;
		index++;
	}

	return GP_OK;
}

static inline uint8_t *
put_dword (uint8_t * ptr, uint32_t value)
{
	ptr[0] = (value & 0xff);
	ptr[1] = (value & 0xff00) >> 8;
	ptr[2] = (value & 0xff0000) >> 16;
	ptr[3] = (value & 0xff000000) >> 24;
	return ptr + 4;
}
