1 /*
2  * STV0680 Vision Camera Chipset Driver
3  * Copyright 2000 Adam Harrison <adam@antispin.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  */
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 
27 #include <gphoto2/gphoto2.h>
28 #include <gphoto2/gphoto2-port.h>
29 
30 #include "stv0680.h"
31 #include "library.h"
32 #include "sharpen.h"
33 #include "bayer.h"
34 #include "saturate.h"
35 #include "../../libgphoto2/bayer.h"
36 #include "demosaic_sharpen.h"
37 
38 #ifdef ENABLE_NLS
39 #  include <libintl.h>
40 #  undef _
41 #  define _(String) dgettext (GETTEXT_PACKAGE, String)
42 #  ifdef gettext_noop
43 #    define N_(String) gettext_noop (String)
44 #  else
45 #    define N_(String) (String)
46 #  endif
47 #else
48 #  define _(String) (String)
49 #  define N_(String) (String)
50 #endif
51 
52 #define CMD_RETRIES		0x03
53 
54 #define STV0680_QCIF_WIDTH	178
55 #define STV0680_QCIF_HEIGHT	146
56 #define STV0680_CIF_WIDTH	356
57 #define STV0680_CIF_HEIGHT	292
58 #define STV0680_QVGA_WIDTH	324
59 #define STV0680_QVGA_HEIGHT	244
60 #define STV0680_VGA_WIDTH	644
61 #define STV0680_VGA_HEIGHT	484
62 
63 static unsigned char
stv0680_checksum(const unsigned char * data,int start,int end)64 stv0680_checksum(const unsigned char *data, int start, int end)
65 {
66 	unsigned char sum = 0;
67 	int i;
68 
69 	for(i = start; i <= end; ++i) {
70 		sum += data[i];
71 		sum &= 0xff;
72 	}
73 	return sum;
74 }
75 
stv0680_cmd(GPPort * port,unsigned char cmd,unsigned char data1,unsigned char data2,unsigned char data3,unsigned char * response,unsigned char response_len)76 static int stv0680_cmd(GPPort *port, unsigned char cmd,
77 		unsigned char data1, unsigned char data2, unsigned char data3,
78 		unsigned char *response, unsigned char response_len)
79 {
80 	unsigned char packet[] = { 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x03 };
81 	unsigned char rhdr[6];
82 	int ret;
83 
84 	/* build command packet */
85 	packet[1] = cmd;
86 	packet[2] = response_len;
87 	packet[3] = data1;
88 	packet[4] = data2;
89 	packet[5] = data3;
90 	packet[6] = stv0680_checksum(packet, 1, 5);
91 
92 	/* write to port */
93 	printf("Writing packet to port\n");
94 	if((ret = gp_port_write(port, (char *)packet, 8)) < GP_OK)
95 		return ret;
96 
97 	printf("Reading response header\n");
98 	/* read response header */
99 	if((ret = gp_port_read(port, (char *)rhdr, 6)) != 6)
100 		return ret;
101 
102 	printf("Read response\n");
103 	/* read response */
104 	if((ret = gp_port_read(port, (char *)response, response_len)) != response_len)
105 	    return ret;
106 
107 	printf("Validating packet [0x%X,0x%X,0x%X,0x%X,0x%X,0x%X]\n",
108 	       rhdr[0], rhdr[1], rhdr[2], rhdr[3], rhdr[4], rhdr[5]);
109 	/* validate response */
110 	if(rhdr[0] != 0x02 || rhdr[1] != cmd ||
111 	   rhdr[2] != response_len ||
112 	   rhdr[3] != stv0680_checksum(response, 0, response_len - 1) ||
113 	   rhdr[4] != stv0680_checksum(rhdr, 1, 3) ||
114 	   rhdr[5] != 0x03)
115 		return GP_ERROR_BAD_PARAMETERS;
116 
117 	printf("Packet OK\n");
118 	return GP_OK;
119 }
120 
stv0680_try_cmd(GPPort * port,unsigned char cmd,unsigned short data,unsigned char * response,unsigned char response_len)121 int stv0680_try_cmd(GPPort *port, unsigned char cmd,
122 		unsigned short data,
123 		unsigned char *response, unsigned char response_len
124 ) {
125 	int ret;
126 	switch (port->type) {
127 	case GP_PORT_USB:
128 	    /* Most significant bit set, data flows from camera->host */
129 	    if (cmd & 0x80)
130 		ret=gp_port_usb_msg_read(port,cmd,data,0,(char *)response,response_len);
131 	    else
132 		ret=gp_port_usb_msg_write(port,cmd,data,0,(char *)response,response_len);
133 
134 	    if (ret == response_len)
135 		return GP_OK;
136 	    return ret;
137 	    break;
138 	case GP_PORT_SERIAL: {
139 	    int retries = CMD_RETRIES;
140 	    while(retries--) {
141 		/* data3 was always 0 */
142 		switch(ret=stv0680_cmd(port,cmd,(data>>8)&0xff,data&0xff,0,response,response_len)) {
143 		case GP_ERROR_TIMEOUT:
144 		case GP_ERROR_BAD_PARAMETERS:
145 			break;
146 		default:
147 			return ret;
148 		}
149 	    }
150 	    break;
151 	}
152 	default:
153 	    return GP_ERROR_NOT_SUPPORTED;
154 	}
155 	return GP_ERROR_IO;
156 }
157 
158 static int
stv0680_last_error(GPPort * port,struct stv680_error_info * errinf)159 stv0680_last_error(GPPort *port, struct stv680_error_info *errinf) {
160 	int ret;
161 
162 	ret = stv0680_try_cmd(port, CMDID_CLEAR_COMMS_ERROR, 0,
163 		(void*)errinf, sizeof(*errinf));
164 	if (ret != GP_OK)
165 	    return ret;
166 	return GP_OK;
167 }
168 
stv0680_ping(GPPort * port)169 int stv0680_ping(GPPort *port)
170 {
171 	unsigned char pong[2];
172 	int ret;
173 
174 	ret=stv0680_try_cmd(port, CMDID_PING, 0x55AA, pong, sizeof(pong));
175 	if (ret != GP_OK)
176 	    return ret;
177 	if ((pong[0]!=0x55) || (pong[1]!=0xAA)) {
178 	    printf("CMDID_PING successful, but returned bad values?\n");
179 	    return GP_ERROR_IO;
180 	}
181 	return GP_OK;
182 }
183 
stv0680_file_count(GPPort * port,int * count)184 int stv0680_file_count(GPPort *port, int *count)
185 {
186 	struct stv680_image_info imginfo;
187 	int ret;
188 
189 	ret = stv0680_try_cmd(port,CMDID_GET_IMAGE_INFO,0,
190 		(void*)&imginfo,sizeof(imginfo));
191 	if (ret != GP_OK)
192 	    return ret;
193 	*count = (imginfo.index[0]<<8)|imginfo.index[1];
194 	return GP_OK;
195 }
196 
197 /**
198  * Get image, with just bayer applied.
199  */
stv0680_get_image_raw(GPPort * port,int image_no,CameraFile * file)200 int stv0680_get_image_raw(GPPort *port, int image_no, CameraFile *file)
201 {
202 	struct stv680_image_header imghdr;
203 	char header[80];
204 	unsigned char *raw, *data;
205 	int h,w,ret,size;
206 
207 	ret = stv0680_try_cmd(port, CMDID_UPLOAD_IMAGE, image_no,
208 		(void*)&imghdr, sizeof(imghdr));
209 	if(ret != GP_OK)
210 	    return ret;
211 
212 	w = (imghdr.width[0]  << 8) | imghdr.width[1];
213 	h = (imghdr.height[0] << 8) | imghdr.height[1];
214 	size = (imghdr.size[0] << 24) | (imghdr.size[1] << 16) |
215 	    (imghdr.size[2]<<8) | imghdr.size[3];
216 	raw = malloc(size);
217 	if (!raw)
218 	    return GP_ERROR_NO_MEMORY;
219 	if ((ret=gp_port_read(port, (char *)raw, size))<0) {
220 	    free (raw);
221 	    return ret;
222 	}
223 
224 	sprintf(header, "P6\n# gPhoto2 stv0680 image\n%d %d\n255\n", w, h);
225 	gp_file_append(file, header, strlen(header));
226 	data = malloc(size * 3);
227 	if (!data) {
228 	    free (raw);
229 	    return GP_ERROR_NO_MEMORY;
230 	}
231 	gp_bayer_decode(raw,w,h,data,BAYER_TILE_GBRG_INTERLACED);
232 	free(raw);
233 	gp_file_append(file, (char *)data, size*3);
234 	free(data);
235 	return GP_OK;
236 }
237 
stv0680_get_image(GPPort * port,int image_no,CameraFile * file)238 int stv0680_get_image(GPPort *port, int image_no, CameraFile *file)
239 {
240 	struct stv680_image_header imghdr;
241 	char header[200];
242 	unsigned char buf[16];
243 	unsigned char *raw, *tmpdata1, *tmpdata2, *data;
244 	int h,w,ret,coarse,fine,size;
245 
246 	/* Despite the documentation saying so, CMDID_UPLOAD_IMAGE does not
247 	 * return an image_header. The first 8 byte are correct, but the
248 	 * next 8 appear strange. So we call CMDID_GET_IMAGE_HEADER before.
249 	 */
250 	ret = stv0680_try_cmd(port, CMDID_GET_IMAGE_HEADER, image_no,
251 		(void*)&imghdr, sizeof(imghdr));
252 	if(ret != GP_OK)
253 		return ret;
254 	ret = stv0680_try_cmd(port, CMDID_UPLOAD_IMAGE, image_no,
255 		(void*)buf, sizeof(buf));
256 	if(ret != GP_OK)
257 		return ret;
258 	w = (imghdr.width[0]  << 8) | imghdr.width[1];
259 	h = (imghdr.height[0] << 8) | imghdr.height[1];
260 	size = (imghdr.size[0] << 24) | (imghdr.size[1] << 16) |
261 	    (imghdr.size[2]<<8) | imghdr.size[3];
262 	fine = (imghdr.fine_exposure[0]<<8)|imghdr.fine_exposure[1];
263 	coarse = (imghdr.coarse_exposure[0]<<8)|imghdr.coarse_exposure[1];
264 	raw = malloc(size);
265 	if (!raw)
266 	    return GP_ERROR_NO_MEMORY;
267 	sprintf(header, "P6\n# gPhoto2 stv0680 image\n#flags %x sgain %d sclkdiv %d avgpix %d fine %d coarse %d\n%d %d\n255\n", imghdr.flags, imghdr.sensor_gain, imghdr.sensor_clkdiv, imghdr.avg_pixel_value, fine, coarse , w, h);
268 
269 	gp_file_append(file, header, strlen(header));
270 	if ((ret=gp_port_read(port, (char *)raw, size))<0) {
271 		free (raw);
272 		return ret;
273 	}
274 
275 	data = malloc(size * 3);
276 	if (!data) {
277 		free (raw);
278 		return GP_ERROR_NO_MEMORY;
279 	}
280 	tmpdata1 = malloc(size * 3);
281 	if (!tmpdata1) {
282 		free (raw);
283 		free (data);
284 		return GP_ERROR_NO_MEMORY;
285 	}
286 	tmpdata2 = malloc(size * 3);
287 	if (!tmpdata2) {
288 		free (raw);
289 		free (data);
290 		free (tmpdata1);
291 		return GP_ERROR_NO_MEMORY;
292 	}
293 	gp_bayer_expand (raw, w, h, tmpdata1, BAYER_TILE_GBRG_INTERLACED);
294 	light_enhance(w,h,coarse,imghdr.avg_pixel_value,fine,tmpdata1);
295 	/* gp_bayer_interpolate (tmpdata1, w, h, BAYER_TILE_GBRG_INTERLACED); */
296 	stv680_hue_saturation (w, h, tmpdata1, tmpdata2 );
297 	demosaic_sharpen (w, h, tmpdata2, tmpdata1, 2, BAYER_TILE_GBRG_INTERLACED);
298 	sharpen (w, h, tmpdata1, data, 16);
299 	free(tmpdata2);
300 	free(tmpdata1);
301 	free(raw);
302 	gp_file_append(file, (char *)data, 3*size);
303 	free(data);
304 	return GP_OK;
305 }
306 
stv0680_get_image_preview(GPPort * port,int image_no,CameraFile * file)307 int stv0680_get_image_preview(GPPort *port, int image_no, CameraFile *file)
308 {
309 	struct stv680_image_header imghdr;
310 	char header[64];
311 	unsigned char *raw, *data;
312 	unsigned int h,w,rh,rw,scale,rsize,size;
313 	int ret;
314 
315 	switch (port->type) {
316 	case GP_PORT_USB:
317 		if ((ret = stv0680_try_cmd(port, CMDID_UPLOAD_IMAGE,
318 			image_no, (void*)&imghdr, sizeof(imghdr)) < 0)) {
319 			return ret;
320 		}
321 		rw = (imghdr.width[0]  << 8) | imghdr.width[1];
322 		rh = (imghdr.height[0] << 8) | imghdr.height[1];
323 		rsize = (imghdr.size[0] << 24) | (imghdr.size[1] << 16) |
324 		    (imghdr.size[2]<<8) | imghdr.size[3];
325 		scale = (rw>>8)+1;
326 		break;
327 	default:
328 	case GP_PORT_SERIAL:
329 		ret = stv0680_try_cmd(port, CMDID_UPLOAD_THUMBNAIL, image_no,
330 			(void*)&imghdr, sizeof(imghdr));
331 		if(ret != GP_OK)
332 			return ret;
333 		rw = (imghdr.width[0]  << 8) | imghdr.width[1];
334 		rh = (imghdr.height[0] << 8) | imghdr.height[1];
335 		rsize = (imghdr.size[0] << 24) | (imghdr.size[1] << 16) |
336 		    (imghdr.size[2]<<8) | imghdr.size[3];
337 		scale = 0;
338 		break;
339 	}
340 	raw = calloc(1, rsize);
341 	if (!raw) return GP_ERROR_NO_MEMORY;
342 	if ((ret=gp_port_read(port, (char *)raw, rsize))<0) {
343 		free(raw);
344 		return ret;
345 	}
346 	w = rw >> scale;
347 	h = rh >> scale;
348 	size = w * h;
349 
350 	sprintf(header, "P6\n# gPhoto2 stv0680 image\n%d %d\n255\n", w, h);
351 	gp_file_append(file, header, strlen(header));
352 	data = calloc(1,size * 3);
353 
354 	if (!scale)
355 	    gp_bayer_decode(raw, rw, rh, data, BAYER_TILE_GBRG_INTERLACED);
356 	else
357 	    bayer_unshuffle_preview(rw, rh, scale, raw, data);
358 	free(raw);
359 	gp_file_append(file, (char *)data, size*3);
360 	free(data);
361 	return GP_OK;
362 }
363 
stv0680_capture(GPPort * port)364 int stv0680_capture(GPPort *port)
365 {
366 	int ret;
367 	struct stv680_error_info errinf;
368 
369 	ret = stv0680_try_cmd(port, CMDID_GRAB_IMAGE, GRAB_UPDATE_INDEX|GRAB_USE_CAMERA_INDEX, NULL, 0);
370 	if (ret!=GP_OK)
371 		return ret;
372 	/* wait until it is done */
373 	do {
374 	    ret = stv0680_last_error(port, &errinf);
375 	    if (ret != GP_OK)
376 		return ret;
377 	    if (errinf.error == CAMERR_BAD_EXPOSURE) {
378 		gp_port_set_error(port,_("Bad exposure (not enough light probably)"));
379 		return GP_ERROR;
380 	    }
381 	    if (errinf.error != CAMERR_BUSY)
382 		fprintf(stderr,"stv680_capture: error was %d.%d\n",errinf.error,errinf.info);
383 	} while (errinf.error == CAMERR_BUSY);
384 	return GP_OK;
385 }
386 
387 #if 0
388 /* this somehow terminates after some images. timeouts due to low exposure?
389  * I haven't found out yet. But this would be the right way to do it.
390  */
391 int stv0680_capture_preview(GPPort *port, char **data, int *size)
392 {
393 	struct stv680_image_header imghdr;
394 	struct stv680_image_info imginfo;
395 	struct stv680_error_info errinf;
396 	char header[64];
397 	unsigned char *raw, *bayerpre;
398 	int fine,coarse,h,w,ret;
399 
400 	ret = stv0680_last_error(port, &errinf);
401 	if (ret != GP_OK)
402 	    return ret;
403 
404 	ret = stv0680_try_cmd(port, CMDID_GRAB_IMAGE, GRAB_USE_CAMERA_INDEX, NULL,0);
405 	if(ret != GP_OK)
406 		return ret;
407 	do {
408 		ret = stv0680_try_cmd(port, CMDID_CLEAR_COMMS_ERROR, 0, (void*)&errinf, sizeof(errinf));
409 		if (ret != GP_OK)
410 		    return ret;
411 		if (errinf.error == CAMERR_BAD_EXPOSURE) {
412 		    gp_port_set_error(port,_("Bad exposure (not enough light probably)"));
413 		    return GP_ERROR;
414 		}
415 		if (errinf.error != CAMERR_BUSY)
416 		    fprintf(stderr,"stv680_capture: error was %d.%d\n",errinf.error,errinf.info);
417 	} while (errinf.error == CAMERR_BUSY);
418 
419 #if 0
420 	fprintf(stderr,"image flag %x\n",imghdr.flags);
421 	fprintf(stderr,"sensor gain %d\n",imghdr.sensor_gain);
422 	fprintf(stderr,"sensor clkdiv %d\n",imghdr.sensor_clkdiv);
423 	fprintf(stderr,"avg pixel value %d\n",imghdr.avg_pixel_value);
424 #endif
425 	ret = stv0680_try_cmd(port, CMDID_GET_IMAGE_INFO, 0, (void*)&imginfo, sizeof(imginfo));
426 	if (ret!=GP_OK)
427 	    return ret;
428 	ret = stv0680_try_cmd(port, CMDID_UPLOAD_IMAGE, (imginfo.index[0]<<8)|imginfo.index[1], (void*)&imghdr, sizeof(imghdr));
429 	if(ret != GP_OK)
430 		return ret;
431 	fine = (imghdr.fine_exposure[0]<<8)|imghdr.fine_exposure[1];
432 	coarse = (imghdr.coarse_exposure[0]<<8)|imghdr.coarse_exposure[1];
433 	w = (imghdr.width[0]  << 8) | imghdr.width[1];
434 	h = (imghdr.height[0] << 8) | imghdr.height[1];
435 	*size = (imghdr.size[0] << 24) | (imghdr.size[1] << 16) |
436 	    (imghdr.size[2]<<8) | imghdr.size[3];
437 
438 	raw = malloc(*size);
439 	if ((ret=gp_port_read(port, raw, *size))<0)
440 	    return ret;
441 
442 	sprintf(header, "P6\n# gPhoto2 stv0680 image\n%d %d\n255\n", w, h);
443 	*data = malloc(((*size) * 3) + strlen(header));
444 	strcpy(*data, header);
445 	bayerpre = malloc(((*size)*3));
446 	gp_bayer_expand (raw, w, h, bayerpre, BAYER_TILE_GBRG_INTERLACED);
447 	light_enhance(w,h,coarse,fine,imghdr.avg_pixel_value,bayerpre);
448 	/* gp_bayer_interpolate (bayerpre, w, h, BAYER_TILE_GBRG_INTERLACED); */
449 	demosaic_sharpen (w, h, bayerpre, *data + strlen(header), 2, BAYER_TILE_GBRG_INTERLACED);
450 	/* sharpen (w, h, bayerpre,*data + strlen(header), 20); */
451 	free(bayerpre);
452 	free(raw);
453 	*size *= 3;
454 	*size += strlen(header);
455 	return GP_OK;
456 }
457 #endif
458 
stv0680_capture_preview(GPPort * port,char ** data,int * size)459 int stv0680_capture_preview(GPPort *port, char **data, int *size)
460 {
461 	char header[64];
462 	struct stv680_camera_info caminfo;
463 	unsigned char *raw,*bayerpre;
464 	int xsize,h,w,i;
465 	int ret;
466 	struct capmode {
467 	    int mask,w,h,mode;
468 	} capmodes[4] = {
469 	    { 1, STV0680_CIF_WIDTH,  STV0680_CIF_HEIGHT,  0x000 },
470 	    { 2, STV0680_VGA_WIDTH,  STV0680_VGA_HEIGHT,  0x100 },
471 	    { 4, STV0680_QCIF_WIDTH, STV0680_QCIF_HEIGHT, 0x200 },
472 	    { 8, STV0680_QVGA_WIDTH, STV0680_QVGA_HEIGHT, 0x300 },
473 	};
474 
475 	/* Get Camera Information */
476 	if ((ret = stv0680_try_cmd(port, CMDID_GET_CAMERA_INFO, 0,
477 				(void*)&caminfo, sizeof(caminfo)) < 0))
478 		return ret;
479 
480 	/* serial cameras don't have video... Too slow. */
481 	if (!(caminfo.hardware_config & HWCONFIG_HAS_VIDEO))
482 	    return GP_ERROR_NOT_SUPPORTED;
483 
484 	/* Look for the first supported mode, with decreasing size */
485 	for (i=0;i<4;i++)
486 	    if (caminfo.capabilities & capmodes[i].mask)
487 		break;
488 	if (i==4) {
489 	    fprintf(stderr,"Neither CIF, QCIF, QVGA nor VGA supported?\n");
490 	    return GP_ERROR;
491 	}
492 	w = capmodes[i].w;
493 	h = capmodes[i].h;
494 
495 	xsize = (w+2)*(h+2);
496 
497 	/* Send parameter according to mode */
498 	ret = stv0680_try_cmd(port,CMDID_START_VIDEO,capmodes[i].mode,NULL,0x0);
499 
500 	if (ret!=GP_OK)
501 		return ret;
502 
503 	*size= xsize;
504 	raw = malloc(*size);
505 	switch(gp_port_read(port, (char *)raw, *size)) {
506 	case GP_ERROR_TIMEOUT:
507 		printf("read timeout\n");
508 		break;
509 	case GP_ERROR:
510 		printf("IO error\n");
511 		break;
512 	default:break;
513 	}
514 	if ((ret = stv0680_try_cmd(port, CMDID_STOP_VIDEO, 0, NULL, 0)!=GP_OK)) {
515 		free (raw);
516 		return ret;
517 	}
518 	sprintf(header, "P6\n# gPhoto2 stv0680 image\n%d %d\n255\n", w, h);
519 	*data = malloc((*size * 3) + strlen(header));
520 	strcpy(*data, header);
521 	bayerpre = malloc(((*size)*3));
522 	/* no light enhancement here, we do not get the exposure values? */
523 	gp_bayer_decode (raw, w, h, bayerpre, BAYER_TILE_GBRG_INTERLACED);
524 	demosaic_sharpen (w, h, bayerpre, (unsigned char *)*data + strlen(header), 2, BAYER_TILE_GBRG_INTERLACED);
525 	/* sharpen (w, h, bayerpre,*data + strlen(header), 20); */
526 	free(raw);
527 	free(bayerpre);
528 	*size *= 3;
529 	*size += strlen(header);
530 	return GP_OK;
531 }
532 
533 
stv0680_delete_all(GPPort * port)534 int stv0680_delete_all(GPPort *port) {
535     return stv0680_try_cmd(port,CMDID_SET_IMAGE_INDEX,0,NULL,0);
536 }
537 
stv0680_summary(GPPort * port,char * txt)538 int stv0680_summary(GPPort *port, char *txt)
539 {
540     struct stv680_camera_info caminfo;
541     struct stv680_image_info imginfo;
542     int ret;
543 
544     strcpy(txt,_("Information on STV0680-based camera:\n"));
545     /* Get Camera Information */
546     if ((ret = stv0680_try_cmd(port, CMDID_GET_CAMERA_INFO,
547 				0, (void*)&caminfo, sizeof(caminfo)) < 0))
548 	return ret;
549     sprintf(txt+strlen(txt),_("Firmware Revision: %d.%d\n"),
550 	    caminfo.firmware_revision[0],
551 	    caminfo.firmware_revision[1]
552     );
553     sprintf(txt+strlen(txt),_("ASIC Revision: %d.%d\n"),
554 	    caminfo.asic_revision[0],
555 	    caminfo.asic_revision[1]
556     );
557     sprintf(txt+strlen(txt),_("Sensor ID: %d.%d\n"),
558 	    caminfo.sensor_id[0],
559 	    caminfo.sensor_id[1]
560     );
561     /* HWCONFIG_COMMSLINK_MASK ... not really needed, the user knows, he
562      * plugged it in. */
563     sprintf(txt+strlen(txt),_("Camera is configured for lights flickering by %dHz.\n"),
564     	(caminfo.hardware_config & HWCONFIG_FLICKERFREQ_60HZ)?60:50
565     );
566     sprintf(txt+strlen(txt),_("Memory in camera: %d Mbit.\n"),
567     	(caminfo.hardware_config & HWCONFIG_MEMSIZE_16MBIT)?16:64
568     );
569     if (caminfo.hardware_config & HWCONFIG_HAS_THUMBNAILS)
570 	strcat(txt,_("Camera supports Thumbnails.\n"));
571     if (caminfo.hardware_config & HWCONFIG_HAS_VIDEO)
572 	strcat(txt,_("Camera supports Video.\n"));
573     /* HWCONFIG_STARTUP_COMPLETED ... Would the camera even answer if not ? */
574     if (caminfo.hardware_config & HWCONFIG_IS_MONOCHROME)
575 	strcat(txt,_("Camera pictures are monochrome.\n"));
576     if (caminfo.hardware_config & HWCONFIG_MEM_FITTED) /* Is this useful? */
577 	strcat(txt,_("Camera has memory.\n"));
578 
579     strcat(txt,_("Camera supports videoformats: "));
580     if (caminfo.capabilities & CAP_CIF) strcat(txt,"CIF ");
581     if (caminfo.capabilities & CAP_VGA) strcat(txt,"VGA ");
582     if (caminfo.capabilities & CAP_QCIF) strcat(txt,"QCIF ");
583     if (caminfo.capabilities & CAP_QVGA) strcat(txt,"QVGA ");
584     strcat(txt,"\n");
585     sprintf(txt+strlen(txt),_("Vendor ID: %02x%02x\n"),
586 	    caminfo.vendor_id[0],
587 	    caminfo.vendor_id[1]
588     );
589     sprintf(txt+strlen(txt),_("Product ID: %02x%02x\n"),
590 	    caminfo.product_id[0],
591 	    caminfo.product_id[1]
592     );
593     if ((ret = stv0680_try_cmd(port, CMDID_GET_IMAGE_INFO, 0,
594 		    (void*)&imginfo, sizeof(imginfo))!=GP_OK))
595 	return ret;
596     sprintf(txt+strlen(txt),_("Number of Images: %d\n"),
597 	    (imginfo.index[0]<<8)|imginfo.index[1]
598     );
599     sprintf(txt+strlen(txt),_("Maximum number of Images: %d\n"),
600 	    (imginfo.maximages[0]<<8)|imginfo.maximages[1]
601     );
602     sprintf(txt+strlen(txt),_("Image width: %d\n"),
603 	    (imginfo.width[0]<<8)|imginfo.width[1]
604     );
605     sprintf(txt+strlen(txt),_("Image height: %d\n"),
606 	    (imginfo.height[0]<<8)|imginfo.height[1]
607     );
608     sprintf(txt+strlen(txt),_("Image size: %d\n"),
609 	    (imginfo.size[0]<<24)|(imginfo.size[1]<<16)|(imginfo.size[2]<<8)|
610 	     imginfo.size[3]
611     );
612     sprintf(txt+strlen(txt),_("Thumbnail width: %d\n"),imginfo.thumb_width);
613     sprintf(txt+strlen(txt),_("Thumbnail height: %d\n"),imginfo.thumb_height);
614     sprintf(txt+strlen(txt),_("Thumbnail size: %d\n"),
615 	    (imginfo.thumb_size[0]<<8)|imginfo.thumb_size[1]
616     );
617     return GP_OK;
618 }
619