diff options
Diffstat (limited to 'drivers/media/video/gspca/ov534.c')
-rw-r--r-- | drivers/media/video/gspca/ov534.c | 820 |
1 files changed, 611 insertions, 209 deletions
diff --git a/drivers/media/video/gspca/ov534.c b/drivers/media/video/gspca/ov534.c index 3bf15e401693..19e0bc60de14 100644 --- a/drivers/media/video/gspca/ov534.c +++ b/drivers/media/video/gspca/ov534.c | |||
@@ -1,7 +1,8 @@ | |||
1 | /* | 1 | /* |
2 | * ov534/ov772x gspca driver | 2 | * ov534 gspca driver |
3 | * Copyright (C) 2008 Antonio Ospite <ospite@studenti.unina.it> | 3 | * Copyright (C) 2008 Antonio Ospite <ospite@studenti.unina.it> |
4 | * Copyright (C) 2008 Jim Paris <jim@jtan.com> | 4 | * Copyright (C) 2008 Jim Paris <jim@jtan.com> |
5 | * Copyright (C) 2009 Jean-Francois Moine http://moinejf.free.fr | ||
5 | * | 6 | * |
6 | * Based on a prototype written by Mark Ferrell <majortrips@gmail.com> | 7 | * Based on a prototype written by Mark Ferrell <majortrips@gmail.com> |
7 | * USB protocol reverse engineered by Jim Paris <jim@jtan.com> | 8 | * USB protocol reverse engineered by Jim Paris <jim@jtan.com> |
@@ -26,7 +27,7 @@ | |||
26 | 27 | ||
27 | #include "gspca.h" | 28 | #include "gspca.h" |
28 | 29 | ||
29 | #define OV534_REG_ADDRESS 0xf1 /* ? */ | 30 | #define OV534_REG_ADDRESS 0xf1 /* sensor address */ |
30 | #define OV534_REG_SUBADDR 0xf2 | 31 | #define OV534_REG_SUBADDR 0xf2 |
31 | #define OV534_REG_WRITE 0xf3 | 32 | #define OV534_REG_WRITE 0xf3 |
32 | #define OV534_REG_READ 0xf4 | 33 | #define OV534_REG_READ 0xf4 |
@@ -46,9 +47,13 @@ MODULE_LICENSE("GPL"); | |||
46 | /* specific webcam descriptor */ | 47 | /* specific webcam descriptor */ |
47 | struct sd { | 48 | struct sd { |
48 | struct gspca_dev gspca_dev; /* !! must be the first item */ | 49 | struct gspca_dev gspca_dev; /* !! must be the first item */ |
49 | __u32 last_fid; | ||
50 | __u32 last_pts; | 50 | __u32 last_pts; |
51 | int frame_rate; | 51 | u16 last_fid; |
52 | u8 frame_rate; | ||
53 | |||
54 | u8 sensor; | ||
55 | #define SENSOR_OV772X 0 | ||
56 | #define SENSOR_OV965X 1 | ||
52 | }; | 57 | }; |
53 | 58 | ||
54 | /* V4L2 controls supported by the driver */ | 59 | /* V4L2 controls supported by the driver */ |
@@ -63,114 +68,7 @@ static const struct v4l2_pix_format vga_mode[] = { | |||
63 | .priv = 0}, | 68 | .priv = 0}, |
64 | }; | 69 | }; |
65 | 70 | ||
66 | static void ov534_reg_write(struct gspca_dev *gspca_dev, u16 reg, u8 val) | 71 | static const u8 bridge_init_ov722x[][2] = { |
67 | { | ||
68 | struct usb_device *udev = gspca_dev->dev; | ||
69 | int ret; | ||
70 | |||
71 | PDEBUG(D_USBO, "reg=0x%04x, val=0%02x", reg, val); | ||
72 | gspca_dev->usb_buf[0] = val; | ||
73 | ret = usb_control_msg(udev, | ||
74 | usb_sndctrlpipe(udev, 0), | ||
75 | 0x1, | ||
76 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
77 | 0x0, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); | ||
78 | if (ret < 0) | ||
79 | PDEBUG(D_ERR, "write failed"); | ||
80 | } | ||
81 | |||
82 | static u8 ov534_reg_read(struct gspca_dev *gspca_dev, u16 reg) | ||
83 | { | ||
84 | struct usb_device *udev = gspca_dev->dev; | ||
85 | int ret; | ||
86 | |||
87 | ret = usb_control_msg(udev, | ||
88 | usb_rcvctrlpipe(udev, 0), | ||
89 | 0x1, | ||
90 | USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
91 | 0x0, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); | ||
92 | PDEBUG(D_USBI, "reg=0x%04x, data=0x%02x", reg, gspca_dev->usb_buf[0]); | ||
93 | if (ret < 0) | ||
94 | PDEBUG(D_ERR, "read failed"); | ||
95 | return gspca_dev->usb_buf[0]; | ||
96 | } | ||
97 | |||
98 | /* Two bits control LED: 0x21 bit 7 and 0x23 bit 7. | ||
99 | * (direction and output)? */ | ||
100 | static void ov534_set_led(struct gspca_dev *gspca_dev, int status) | ||
101 | { | ||
102 | u8 data; | ||
103 | |||
104 | PDEBUG(D_CONF, "led status: %d", status); | ||
105 | |||
106 | data = ov534_reg_read(gspca_dev, 0x21); | ||
107 | data |= 0x80; | ||
108 | ov534_reg_write(gspca_dev, 0x21, data); | ||
109 | |||
110 | data = ov534_reg_read(gspca_dev, 0x23); | ||
111 | if (status) | ||
112 | data |= 0x80; | ||
113 | else | ||
114 | data &= ~(0x80); | ||
115 | |||
116 | ov534_reg_write(gspca_dev, 0x23, data); | ||
117 | } | ||
118 | |||
119 | static int sccb_check_status(struct gspca_dev *gspca_dev) | ||
120 | { | ||
121 | u8 data; | ||
122 | int i; | ||
123 | |||
124 | for (i = 0; i < 5; i++) { | ||
125 | data = ov534_reg_read(gspca_dev, OV534_REG_STATUS); | ||
126 | |||
127 | switch (data) { | ||
128 | case 0x00: | ||
129 | return 1; | ||
130 | case 0x04: | ||
131 | return 0; | ||
132 | case 0x03: | ||
133 | break; | ||
134 | default: | ||
135 | PDEBUG(D_ERR, "sccb status 0x%02x, attempt %d/5", | ||
136 | data, i + 1); | ||
137 | } | ||
138 | } | ||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | static void sccb_reg_write(struct gspca_dev *gspca_dev, u16 reg, u8 val) | ||
143 | { | ||
144 | PDEBUG(D_USBO, "reg: 0x%04x, val: 0x%02x", reg, val); | ||
145 | ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg); | ||
146 | ov534_reg_write(gspca_dev, OV534_REG_WRITE, val); | ||
147 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_3); | ||
148 | |||
149 | if (!sccb_check_status(gspca_dev)) | ||
150 | PDEBUG(D_ERR, "sccb_reg_write failed"); | ||
151 | } | ||
152 | |||
153 | #ifdef GSPCA_DEBUG | ||
154 | static u8 sccb_reg_read(struct gspca_dev *gspca_dev, u16 reg) | ||
155 | { | ||
156 | ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg); | ||
157 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_2); | ||
158 | if (!sccb_check_status(gspca_dev)) | ||
159 | PDEBUG(D_ERR, "sccb_reg_read failed 1"); | ||
160 | |||
161 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_READ_2); | ||
162 | if (!sccb_check_status(gspca_dev)) | ||
163 | PDEBUG(D_ERR, "sccb_reg_read failed 2"); | ||
164 | |||
165 | return ov534_reg_read(gspca_dev, OV534_REG_READ); | ||
166 | } | ||
167 | #endif | ||
168 | |||
169 | static const __u8 ov534_reg_initdata[][2] = { | ||
170 | { 0xe7, 0x3a }, | ||
171 | |||
172 | { OV534_REG_ADDRESS, 0x42 }, /* select OV772x sensor */ | ||
173 | |||
174 | { 0xc2, 0x0c }, | 72 | { 0xc2, 0x0c }, |
175 | { 0x88, 0xf8 }, | 73 | { 0x88, 0xf8 }, |
176 | { 0xc3, 0x69 }, | 74 | { 0xc3, 0x69 }, |
@@ -228,7 +126,7 @@ static const __u8 ov534_reg_initdata[][2] = { | |||
228 | { 0xc2, 0x0c }, | 126 | { 0xc2, 0x0c }, |
229 | }; | 127 | }; |
230 | 128 | ||
231 | static const __u8 ov772x_reg_initdata[][2] = { | 129 | static const u8 sensor_init_ov722x[][2] = { |
232 | { 0x12, 0x80 }, | 130 | { 0x12, 0x80 }, |
233 | { 0x11, 0x01 }, | 131 | { 0x11, 0x01 }, |
234 | 132 | ||
@@ -311,6 +209,456 @@ static const __u8 ov772x_reg_initdata[][2] = { | |||
311 | { 0x0c, 0xd0 } | 209 | { 0x0c, 0xd0 } |
312 | }; | 210 | }; |
313 | 211 | ||
212 | static const u8 bridge_init_ov965x[][2] = { | ||
213 | {0x88, 0xf8}, | ||
214 | {0x89, 0xff}, | ||
215 | {0x76, 0x03}, | ||
216 | {0x92, 0x03}, | ||
217 | {0x95, 0x10}, | ||
218 | {0xe2, 0x00}, | ||
219 | {0xe7, 0x3e}, | ||
220 | {0x8d, 0x1c}, | ||
221 | {0x8e, 0x00}, | ||
222 | {0x8f, 0x00}, | ||
223 | {0x1f, 0x00}, | ||
224 | {0xc3, 0xf9}, | ||
225 | {0x89, 0xff}, | ||
226 | {0x88, 0xf8}, | ||
227 | {0x76, 0x03}, | ||
228 | {0x92, 0x01}, | ||
229 | {0x93, 0x18}, | ||
230 | {0x1c, 0x0a}, | ||
231 | {0x1d, 0x48}, | ||
232 | {0xc0, 0x50}, | ||
233 | {0xc1, 0x3c}, | ||
234 | {0x34, 0x05}, | ||
235 | {0xc2, 0x0c}, | ||
236 | {0xc3, 0xf9}, | ||
237 | {0x34, 0x05}, | ||
238 | {0xe7, 0x2e}, | ||
239 | {0x31, 0xf9}, | ||
240 | {0x35, 0x02}, | ||
241 | {0xd9, 0x10}, | ||
242 | {0x25, 0x42}, | ||
243 | {0x94, 0x11}, | ||
244 | }; | ||
245 | |||
246 | static const u8 sensor_init_ov965x[][2] = { | ||
247 | {0x12, 0x80}, /* com7 - reset */ | ||
248 | {0x00, 0x00}, /* gain */ | ||
249 | {0x01, 0x80}, /* blue */ | ||
250 | {0x02, 0x80}, /* red */ | ||
251 | {0x03, 0x1b}, /* vref */ | ||
252 | {0x04, 0x03}, /* com1 - exposure low bits */ | ||
253 | {0x0b, 0x57}, /* ver */ | ||
254 | {0x0e, 0x61}, /* com5 */ | ||
255 | {0x0f, 0x42}, /* com6 */ | ||
256 | {0x11, 0x00}, /* clkrc */ | ||
257 | {0x12, 0x02}, /* com7 */ | ||
258 | {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ | ||
259 | {0x14, 0x28}, /* com9 */ | ||
260 | {0x16, 0x24}, /* rsvd16 */ | ||
261 | {0x17, 0x1d}, /* hstart*/ | ||
262 | {0x18, 0xbd}, /* hstop */ | ||
263 | {0x19, 0x01}, /* vstrt */ | ||
264 | {0x1a, 0x81}, /* vstop*/ | ||
265 | {0x1e, 0x04}, /* mvfp */ | ||
266 | {0x24, 0x3c}, /* aew */ | ||
267 | {0x25, 0x36}, /* aeb */ | ||
268 | {0x26, 0x71}, /* vpt */ | ||
269 | {0x27, 0x08}, /* bbias */ | ||
270 | {0x28, 0x08}, /* gbbias */ | ||
271 | {0x29, 0x15}, /* gr com */ | ||
272 | {0x2a, 0x00}, | ||
273 | {0x2b, 0x00}, | ||
274 | {0x2c, 0x08}, /* rbias */ | ||
275 | {0x32, 0xff}, /* href */ | ||
276 | {0x33, 0x00}, /* chlf */ | ||
277 | {0x34, 0x3f}, /* arblm */ | ||
278 | {0x35, 0x00}, /* rsvd35 */ | ||
279 | {0x36, 0xf8}, /* rsvd36 */ | ||
280 | {0x38, 0x72}, /* acom38 */ | ||
281 | {0x39, 0x57}, /* ofon */ | ||
282 | {0x3a, 0x80}, /* tslb */ | ||
283 | {0x3b, 0xc4}, | ||
284 | {0x3d, 0x99}, /* com13 */ | ||
285 | {0x3f, 0xc1}, | ||
286 | {0x40, 0xc0}, /* com15 */ | ||
287 | {0x41, 0x40}, /* com16 */ | ||
288 | {0x42, 0xc0}, | ||
289 | {0x43, 0x0a}, | ||
290 | {0x44, 0xf0}, | ||
291 | {0x45, 0x46}, | ||
292 | {0x46, 0x62}, | ||
293 | {0x47, 0x2a}, | ||
294 | {0x48, 0x3c}, | ||
295 | {0x4a, 0xfc}, | ||
296 | {0x4b, 0xfc}, | ||
297 | {0x4c, 0x7f}, | ||
298 | {0x4d, 0x7f}, | ||
299 | {0x4e, 0x7f}, | ||
300 | {0x4f, 0x98}, | ||
301 | {0x50, 0x98}, | ||
302 | {0x51, 0x00}, | ||
303 | {0x52, 0x28}, | ||
304 | {0x53, 0x70}, | ||
305 | {0x54, 0x98}, | ||
306 | {0x58, 0x1a}, | ||
307 | {0x59, 0x85}, | ||
308 | {0x5a, 0xa9}, | ||
309 | {0x5b, 0x64}, | ||
310 | {0x5c, 0x84}, | ||
311 | {0x5d, 0x53}, | ||
312 | {0x5e, 0x0e}, | ||
313 | {0x5f, 0xf0}, | ||
314 | {0x60, 0xf0}, | ||
315 | {0x61, 0xf0}, | ||
316 | {0x62, 0x00}, /* lcc1 */ | ||
317 | {0x63, 0x00}, /* lcc2 */ | ||
318 | {0x64, 0x02}, /* lcc3 */ | ||
319 | {0x65, 0x16}, /* lcc4 */ | ||
320 | {0x66, 0x01}, /* lcc5 */ | ||
321 | {0x69, 0x02}, /* hv */ | ||
322 | {0x6b, 0x5a}, /* dbvl */ | ||
323 | {0x6c, 0x04}, | ||
324 | {0x6d, 0x55}, | ||
325 | {0x6e, 0x00}, | ||
326 | {0x6f, 0x9d}, | ||
327 | {0x70, 0x21}, | ||
328 | {0x71, 0x78}, | ||
329 | {0x72, 0x00}, | ||
330 | {0x73, 0x01}, | ||
331 | {0x74, 0x3a}, | ||
332 | {0x75, 0x35}, | ||
333 | {0x76, 0x01}, | ||
334 | {0x77, 0x02}, | ||
335 | {0x7a, 0x12}, | ||
336 | {0x7b, 0x08}, | ||
337 | {0x7c, 0x16}, | ||
338 | {0x7d, 0x30}, | ||
339 | {0x7e, 0x5e}, | ||
340 | {0x7f, 0x72}, | ||
341 | {0x80, 0x82}, | ||
342 | {0x81, 0x8e}, | ||
343 | {0x82, 0x9a}, | ||
344 | {0x83, 0xa4}, | ||
345 | {0x84, 0xac}, | ||
346 | {0x85, 0xb8}, | ||
347 | {0x86, 0xc3}, | ||
348 | {0x87, 0xd6}, | ||
349 | {0x88, 0xe6}, | ||
350 | {0x89, 0xf2}, | ||
351 | {0x8a, 0x03}, | ||
352 | {0x8c, 0x89}, | ||
353 | {0x14, 0x28}, /* com9 */ | ||
354 | {0x90, 0x7d}, | ||
355 | {0x91, 0x7b}, | ||
356 | {0x9d, 0x03}, | ||
357 | {0x9e, 0x04}, | ||
358 | {0x9f, 0x7a}, | ||
359 | {0xa0, 0x79}, | ||
360 | {0xa1, 0x40}, /* aechm */ | ||
361 | {0xa4, 0x50}, | ||
362 | {0xa5, 0x68}, /* com26 */ | ||
363 | {0xa6, 0x4a}, | ||
364 | {0xa8, 0xc1}, /* acoma8 */ | ||
365 | {0xa9, 0xef}, /* acoma9 */ | ||
366 | {0xaa, 0x92}, | ||
367 | {0xab, 0x04}, | ||
368 | {0xac, 0x80}, | ||
369 | {0xad, 0x80}, | ||
370 | {0xae, 0x80}, | ||
371 | {0xaf, 0x80}, | ||
372 | {0xb2, 0xf2}, | ||
373 | {0xb3, 0x20}, | ||
374 | {0xb4, 0x20}, | ||
375 | {0xb5, 0x00}, | ||
376 | {0xb6, 0xaf}, | ||
377 | {0xbb, 0xae}, | ||
378 | {0xbc, 0x7f}, | ||
379 | {0xdb, 0x7f}, | ||
380 | {0xbe, 0x7f}, | ||
381 | {0xbf, 0x7f}, | ||
382 | {0xc0, 0xe2}, | ||
383 | {0xc1, 0xc0}, | ||
384 | {0xc2, 0x01}, | ||
385 | {0xc3, 0x4e}, | ||
386 | {0xc6, 0x85}, | ||
387 | {0xc7, 0x80}, | ||
388 | {0xc9, 0xe0}, | ||
389 | {0xca, 0xe8}, | ||
390 | {0xcb, 0xf0}, | ||
391 | {0xcc, 0xd8}, | ||
392 | {0xcd, 0xf1}, | ||
393 | {0x4f, 0x98}, | ||
394 | {0x50, 0x98}, | ||
395 | {0x51, 0x00}, | ||
396 | {0x52, 0x28}, | ||
397 | {0x53, 0x70}, | ||
398 | {0x54, 0x98}, | ||
399 | {0x58, 0x1a}, | ||
400 | {0xff, 0x41}, /* read 41, write ff 00 */ | ||
401 | {0x41, 0x40}, /* com16 */ | ||
402 | {0xc5, 0x03}, | ||
403 | {0x6a, 0x02}, | ||
404 | |||
405 | {0x12, 0x62}, /* com7 - VGA + CIF */ | ||
406 | {0x36, 0xfa}, /* rsvd36 */ | ||
407 | {0x69, 0x0a}, /* hv */ | ||
408 | {0x8c, 0x89}, /* com22 */ | ||
409 | {0x14, 0x28}, /* com9 */ | ||
410 | {0x3e, 0x0c}, | ||
411 | {0x41, 0x40}, /* com16 */ | ||
412 | {0x72, 0x00}, | ||
413 | {0x73, 0x00}, | ||
414 | {0x74, 0x3a}, | ||
415 | {0x75, 0x35}, | ||
416 | {0x76, 0x01}, | ||
417 | {0xc7, 0x80}, | ||
418 | {0x03, 0x12}, /* vref */ | ||
419 | {0x17, 0x16}, /* hstart */ | ||
420 | {0x18, 0x02}, /* hstop */ | ||
421 | {0x19, 0x01}, /* vstrt */ | ||
422 | {0x1a, 0x3d}, /* vstop */ | ||
423 | {0x32, 0xff}, /* href */ | ||
424 | {0xc0, 0xaa}, | ||
425 | }; | ||
426 | |||
427 | static const u8 bridge_init_ov965x_2[][2] = { | ||
428 | {0x94, 0xaa}, | ||
429 | {0xf1, 0x60}, | ||
430 | {0xe5, 0x04}, | ||
431 | {0xc0, 0x50}, | ||
432 | {0xc1, 0x3c}, | ||
433 | {0x8c, 0x00}, | ||
434 | {0x8d, 0x1c}, | ||
435 | {0x34, 0x05}, | ||
436 | |||
437 | {0xc2, 0x0c}, | ||
438 | {0xc3, 0xf9}, | ||
439 | {0xda, 0x01}, | ||
440 | {0x50, 0x00}, | ||
441 | {0x51, 0xa0}, | ||
442 | {0x52, 0x3c}, | ||
443 | {0x53, 0x00}, | ||
444 | {0x54, 0x00}, | ||
445 | {0x55, 0x00}, | ||
446 | {0x57, 0x00}, | ||
447 | {0x5c, 0x00}, | ||
448 | {0x5a, 0xa0}, | ||
449 | {0x5b, 0x78}, | ||
450 | {0x35, 0x02}, | ||
451 | {0xd9, 0x10}, | ||
452 | {0x94, 0x11}, | ||
453 | }; | ||
454 | |||
455 | static const u8 sensor_init_ov965x_2[][2] = { | ||
456 | {0x3b, 0xc4}, | ||
457 | {0x1e, 0x04}, /* mvfp */ | ||
458 | {0x13, 0xe0}, /* com8 */ | ||
459 | {0x00, 0x00}, /* gain */ | ||
460 | {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ | ||
461 | {0x11, 0x03}, /* clkrc */ | ||
462 | {0x6b, 0x5a}, /* dblv */ | ||
463 | {0x6a, 0x05}, | ||
464 | {0xc5, 0x07}, | ||
465 | {0xa2, 0x4b}, | ||
466 | {0xa3, 0x3e}, | ||
467 | {0x2d, 0x00}, | ||
468 | {0xff, 0x42}, /* read 42, write ff 00 */ | ||
469 | {0x42, 0xc0}, | ||
470 | {0x2d, 0x00}, | ||
471 | {0xff, 0x42}, /* read 42, write ff 00 */ | ||
472 | {0x42, 0xc1}, | ||
473 | {0x3f, 0x01}, | ||
474 | {0xff, 0x42}, /* read 42, write ff 00 */ | ||
475 | {0x42, 0xc1}, | ||
476 | {0x4f, 0x98}, | ||
477 | {0x50, 0x98}, | ||
478 | {0x51, 0x00}, | ||
479 | {0x52, 0x28}, | ||
480 | {0x53, 0x70}, | ||
481 | {0x54, 0x98}, | ||
482 | {0x58, 0x1a}, | ||
483 | {0xff, 0x41}, /* read 41, write ff 00 */ | ||
484 | {0x41, 0x40}, /* com16 */ | ||
485 | {0x56, 0x40}, | ||
486 | {0x55, 0x8f}, | ||
487 | {0x10, 0x25}, /* aech - exposure high bits */ | ||
488 | {0xff, 0x13}, /* read 13, write ff 00 */ | ||
489 | {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ | ||
490 | }; | ||
491 | |||
492 | static const u8 bridge_start_ov965x[][2] = { | ||
493 | {0xc2, 0x4c}, | ||
494 | {0xc3, 0xf9}, | ||
495 | {0x50, 0x00}, | ||
496 | {0x51, 0xa0}, | ||
497 | {0x52, 0x78}, | ||
498 | {0x53, 0x00}, | ||
499 | {0x54, 0x00}, | ||
500 | {0x55, 0x00}, | ||
501 | {0x57, 0x00}, | ||
502 | {0x5c, 0x00}, | ||
503 | {0x5a, 0x28}, | ||
504 | {0x5b, 0x1e}, | ||
505 | {0x35, 0x00}, | ||
506 | {0xd9, 0x21}, | ||
507 | {0x94, 0x11}, | ||
508 | }; | ||
509 | |||
510 | static const u8 sensor_start_ov965x[][2] = { | ||
511 | {0x3b, 0xe4}, | ||
512 | {0x1e, 0x04}, /* mvfp */ | ||
513 | {0x13, 0xe0}, /* com8 */ | ||
514 | {0x00, 0x00}, | ||
515 | {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ | ||
516 | {0x11, 0x01}, /* clkrc */ | ||
517 | {0x6b, 0x5a}, /* dblv */ | ||
518 | {0x6a, 0x02}, | ||
519 | {0xc5, 0x03}, | ||
520 | {0xa2, 0x96}, | ||
521 | {0xa3, 0x7d}, | ||
522 | {0xff, 0x13}, /* read 13, write ff 00 */ | ||
523 | {0x13, 0xe7}, | ||
524 | {0x3a, 0x80}, | ||
525 | {0xff, 0x42}, /* read 42, write ff 00 */ | ||
526 | {0x42, 0xc1}, | ||
527 | }; | ||
528 | |||
529 | |||
530 | static void ov534_reg_write(struct gspca_dev *gspca_dev, u16 reg, u8 val) | ||
531 | { | ||
532 | struct usb_device *udev = gspca_dev->dev; | ||
533 | int ret; | ||
534 | |||
535 | PDEBUG(D_USBO, "reg=0x%04x, val=0%02x", reg, val); | ||
536 | gspca_dev->usb_buf[0] = val; | ||
537 | ret = usb_control_msg(udev, | ||
538 | usb_sndctrlpipe(udev, 0), | ||
539 | 0x01, | ||
540 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
541 | 0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); | ||
542 | if (ret < 0) | ||
543 | PDEBUG(D_ERR, "write failed"); | ||
544 | } | ||
545 | |||
546 | static u8 ov534_reg_read(struct gspca_dev *gspca_dev, u16 reg) | ||
547 | { | ||
548 | struct usb_device *udev = gspca_dev->dev; | ||
549 | int ret; | ||
550 | |||
551 | ret = usb_control_msg(udev, | ||
552 | usb_rcvctrlpipe(udev, 0), | ||
553 | 0x01, | ||
554 | USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
555 | 0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); | ||
556 | PDEBUG(D_USBI, "reg=0x%04x, data=0x%02x", reg, gspca_dev->usb_buf[0]); | ||
557 | if (ret < 0) | ||
558 | PDEBUG(D_ERR, "read failed"); | ||
559 | return gspca_dev->usb_buf[0]; | ||
560 | } | ||
561 | |||
562 | /* Two bits control LED: 0x21 bit 7 and 0x23 bit 7. | ||
563 | * (direction and output)? */ | ||
564 | static void ov534_set_led(struct gspca_dev *gspca_dev, int status) | ||
565 | { | ||
566 | u8 data; | ||
567 | |||
568 | PDEBUG(D_CONF, "led status: %d", status); | ||
569 | |||
570 | data = ov534_reg_read(gspca_dev, 0x21); | ||
571 | data |= 0x80; | ||
572 | ov534_reg_write(gspca_dev, 0x21, data); | ||
573 | |||
574 | data = ov534_reg_read(gspca_dev, 0x23); | ||
575 | if (status) | ||
576 | data |= 0x80; | ||
577 | else | ||
578 | data &= ~0x80; | ||
579 | |||
580 | ov534_reg_write(gspca_dev, 0x23, data); | ||
581 | |||
582 | if (!status) { | ||
583 | data = ov534_reg_read(gspca_dev, 0x21); | ||
584 | data &= ~0x80; | ||
585 | ov534_reg_write(gspca_dev, 0x21, data); | ||
586 | } | ||
587 | } | ||
588 | |||
589 | static int sccb_check_status(struct gspca_dev *gspca_dev) | ||
590 | { | ||
591 | u8 data; | ||
592 | int i; | ||
593 | |||
594 | for (i = 0; i < 5; i++) { | ||
595 | data = ov534_reg_read(gspca_dev, OV534_REG_STATUS); | ||
596 | |||
597 | switch (data) { | ||
598 | case 0x00: | ||
599 | return 1; | ||
600 | case 0x04: | ||
601 | return 0; | ||
602 | case 0x03: | ||
603 | break; | ||
604 | default: | ||
605 | PDEBUG(D_ERR, "sccb status 0x%02x, attempt %d/5", | ||
606 | data, i + 1); | ||
607 | } | ||
608 | } | ||
609 | return 0; | ||
610 | } | ||
611 | |||
612 | static void sccb_reg_write(struct gspca_dev *gspca_dev, u8 reg, u8 val) | ||
613 | { | ||
614 | PDEBUG(D_USBO, "reg: 0x%02x, val: 0x%02x", reg, val); | ||
615 | ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg); | ||
616 | ov534_reg_write(gspca_dev, OV534_REG_WRITE, val); | ||
617 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_3); | ||
618 | |||
619 | if (!sccb_check_status(gspca_dev)) | ||
620 | PDEBUG(D_ERR, "sccb_reg_write failed"); | ||
621 | } | ||
622 | |||
623 | static u8 sccb_reg_read(struct gspca_dev *gspca_dev, u16 reg) | ||
624 | { | ||
625 | ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg); | ||
626 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_2); | ||
627 | if (!sccb_check_status(gspca_dev)) | ||
628 | PDEBUG(D_ERR, "sccb_reg_read failed 1"); | ||
629 | |||
630 | ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_READ_2); | ||
631 | if (!sccb_check_status(gspca_dev)) | ||
632 | PDEBUG(D_ERR, "sccb_reg_read failed 2"); | ||
633 | |||
634 | return ov534_reg_read(gspca_dev, OV534_REG_READ); | ||
635 | } | ||
636 | |||
637 | /* output a bridge sequence (reg - val) */ | ||
638 | static void reg_w_array(struct gspca_dev *gspca_dev, | ||
639 | const u8 (*data)[2], int len) | ||
640 | { | ||
641 | while (--len >= 0) { | ||
642 | ov534_reg_write(gspca_dev, (*data)[0], (*data)[1]); | ||
643 | data++; | ||
644 | } | ||
645 | } | ||
646 | |||
647 | /* output a sensor sequence (reg - val) */ | ||
648 | static void sccb_w_array(struct gspca_dev *gspca_dev, | ||
649 | const u8 (*data)[2], int len) | ||
650 | { | ||
651 | while (--len >= 0) { | ||
652 | if ((*data)[0] != 0xff) { | ||
653 | sccb_reg_write(gspca_dev, (*data)[0], (*data)[1]); | ||
654 | } else { | ||
655 | sccb_reg_read(gspca_dev, (*data)[1]); | ||
656 | sccb_reg_write(gspca_dev, 0xff, 0x00); | ||
657 | } | ||
658 | data++; | ||
659 | } | ||
660 | } | ||
661 | |||
314 | /* set framerate */ | 662 | /* set framerate */ |
315 | static void ov534_set_frame_rate(struct gspca_dev *gspca_dev) | 663 | static void ov534_set_frame_rate(struct gspca_dev *gspca_dev) |
316 | { | 664 | { |
@@ -346,40 +694,17 @@ static void ov534_set_frame_rate(struct gspca_dev *gspca_dev) | |||
346 | PDEBUG(D_PROBE, "frame_rate: %d", fr); | 694 | PDEBUG(D_PROBE, "frame_rate: %d", fr); |
347 | } | 695 | } |
348 | 696 | ||
349 | /* setup method */ | ||
350 | static void ov534_setup(struct gspca_dev *gspca_dev) | ||
351 | { | ||
352 | int i; | ||
353 | |||
354 | /* Initialize bridge chip */ | ||
355 | for (i = 0; i < ARRAY_SIZE(ov534_reg_initdata); i++) | ||
356 | ov534_reg_write(gspca_dev, ov534_reg_initdata[i][0], | ||
357 | ov534_reg_initdata[i][1]); | ||
358 | |||
359 | PDEBUG(D_PROBE, "sensor is ov%02x%02x", | ||
360 | sccb_reg_read(gspca_dev, 0x0a), | ||
361 | sccb_reg_read(gspca_dev, 0x0b)); | ||
362 | |||
363 | ov534_set_led(gspca_dev, 1); | ||
364 | |||
365 | /* Initialize sensor */ | ||
366 | for (i = 0; i < ARRAY_SIZE(ov772x_reg_initdata); i++) | ||
367 | sccb_reg_write(gspca_dev, ov772x_reg_initdata[i][0], | ||
368 | ov772x_reg_initdata[i][1]); | ||
369 | |||
370 | ov534_reg_write(gspca_dev, 0xe0, 0x09); | ||
371 | ov534_set_led(gspca_dev, 0); | ||
372 | } | ||
373 | |||
374 | /* this function is called at probe time */ | 697 | /* this function is called at probe time */ |
375 | static int sd_config(struct gspca_dev *gspca_dev, | 698 | static int sd_config(struct gspca_dev *gspca_dev, |
376 | const struct usb_device_id *id) | 699 | const struct usb_device_id *id) |
377 | { | 700 | { |
701 | struct sd *sd = (struct sd *) gspca_dev; | ||
378 | struct cam *cam; | 702 | struct cam *cam; |
379 | 703 | ||
704 | sd->sensor = id->driver_info; | ||
705 | |||
380 | cam = &gspca_dev->cam; | 706 | cam = &gspca_dev->cam; |
381 | 707 | ||
382 | cam->epaddr = 0x01; | ||
383 | cam->cam_mode = vga_mode; | 708 | cam->cam_mode = vga_mode; |
384 | cam->nmodes = ARRAY_SIZE(vga_mode); | 709 | cam->nmodes = ARRAY_SIZE(vga_mode); |
385 | 710 | ||
@@ -392,26 +717,102 @@ static int sd_config(struct gspca_dev *gspca_dev, | |||
392 | /* this function is called at probe and resume time */ | 717 | /* this function is called at probe and resume time */ |
393 | static int sd_init(struct gspca_dev *gspca_dev) | 718 | static int sd_init(struct gspca_dev *gspca_dev) |
394 | { | 719 | { |
395 | ov534_setup(gspca_dev); | 720 | struct sd *sd = (struct sd *) gspca_dev; |
396 | ov534_set_frame_rate(gspca_dev); | 721 | u16 sensor_id; |
722 | static const u8 sensor_addr[2] = { | ||
723 | 0x42, /* 0 SENSOR_OV772X */ | ||
724 | 0x60, /* 1 SENSOR_OV965X */ | ||
725 | }; | ||
726 | |||
727 | /* reset bridge */ | ||
728 | ov534_reg_write(gspca_dev, 0xe7, 0x3a); | ||
729 | ov534_reg_write(gspca_dev, 0xe0, 0x08); | ||
730 | msleep(100); | ||
731 | |||
732 | /* initialize the sensor address */ | ||
733 | ov534_reg_write(gspca_dev, OV534_REG_ADDRESS, | ||
734 | sensor_addr[sd->sensor]); | ||
735 | |||
736 | /* reset sensor */ | ||
737 | sccb_reg_write(gspca_dev, 0x12, 0x80); | ||
738 | msleep(10); | ||
739 | |||
740 | /* probe the sensor */ | ||
741 | sccb_reg_read(gspca_dev, 0x0a); | ||
742 | sensor_id = sccb_reg_read(gspca_dev, 0x0a) << 8; | ||
743 | sccb_reg_read(gspca_dev, 0x0b); | ||
744 | sensor_id |= sccb_reg_read(gspca_dev, 0x0b); | ||
745 | PDEBUG(D_PROBE, "Sensor ID: %04x", sensor_id); | ||
746 | |||
747 | /* initialize */ | ||
748 | switch (sd->sensor) { | ||
749 | case SENSOR_OV772X: | ||
750 | reg_w_array(gspca_dev, bridge_init_ov722x, | ||
751 | ARRAY_SIZE(bridge_init_ov722x)); | ||
752 | ov534_set_led(gspca_dev, 1); | ||
753 | sccb_w_array(gspca_dev, sensor_init_ov722x, | ||
754 | ARRAY_SIZE(sensor_init_ov722x)); | ||
755 | ov534_reg_write(gspca_dev, 0xe0, 0x09); | ||
756 | ov534_set_led(gspca_dev, 0); | ||
757 | ov534_set_frame_rate(gspca_dev); | ||
758 | break; | ||
759 | default: | ||
760 | /* case SENSOR_OV965X: */ | ||
761 | reg_w_array(gspca_dev, bridge_init_ov965x, | ||
762 | ARRAY_SIZE(bridge_init_ov965x)); | ||
763 | sccb_w_array(gspca_dev, sensor_init_ov965x, | ||
764 | ARRAY_SIZE(sensor_init_ov965x)); | ||
765 | reg_w_array(gspca_dev, bridge_init_ov965x_2, | ||
766 | ARRAY_SIZE(bridge_init_ov965x_2)); | ||
767 | sccb_w_array(gspca_dev, sensor_init_ov965x_2, | ||
768 | ARRAY_SIZE(sensor_init_ov965x_2)); | ||
769 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
770 | ov534_reg_write(gspca_dev, 0xe0, 0x01); | ||
771 | ov534_set_led(gspca_dev, 0); | ||
772 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
773 | } | ||
397 | 774 | ||
398 | return 0; | 775 | return 0; |
399 | } | 776 | } |
400 | 777 | ||
401 | static int sd_start(struct gspca_dev *gspca_dev) | 778 | static int sd_start(struct gspca_dev *gspca_dev) |
402 | { | 779 | { |
403 | /* start streaming data */ | 780 | struct sd *sd = (struct sd *) gspca_dev; |
404 | ov534_set_led(gspca_dev, 1); | ||
405 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
406 | 781 | ||
782 | switch (sd->sensor) { | ||
783 | case SENSOR_OV772X: | ||
784 | ov534_set_led(gspca_dev, 1); | ||
785 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
786 | break; | ||
787 | default: | ||
788 | /* case SENSOR_OV965X: */ | ||
789 | reg_w_array(gspca_dev, bridge_start_ov965x, | ||
790 | ARRAY_SIZE(bridge_start_ov965x)); | ||
791 | sccb_w_array(gspca_dev, sensor_start_ov965x, | ||
792 | ARRAY_SIZE(sensor_start_ov965x)); | ||
793 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
794 | ov534_set_led(gspca_dev, 1); | ||
795 | /*fixme: other sensor start omitted*/ | ||
796 | } | ||
407 | return 0; | 797 | return 0; |
408 | } | 798 | } |
409 | 799 | ||
410 | static void sd_stopN(struct gspca_dev *gspca_dev) | 800 | static void sd_stopN(struct gspca_dev *gspca_dev) |
411 | { | 801 | { |
412 | /* stop streaming data */ | 802 | struct sd *sd = (struct sd *) gspca_dev; |
413 | ov534_reg_write(gspca_dev, 0xe0, 0x09); | 803 | |
414 | ov534_set_led(gspca_dev, 0); | 804 | switch (sd->sensor) { |
805 | case SENSOR_OV772X: | ||
806 | ov534_reg_write(gspca_dev, 0xe0, 0x09); | ||
807 | ov534_set_led(gspca_dev, 0); | ||
808 | break; | ||
809 | default: | ||
810 | /* case SENSOR_OV965X: */ | ||
811 | ov534_reg_write(gspca_dev, 0xe0, 0x01); | ||
812 | ov534_set_led(gspca_dev, 0); | ||
813 | ov534_reg_write(gspca_dev, 0xe0, 0x00); | ||
814 | break; | ||
815 | } | ||
415 | } | 816 | } |
416 | 817 | ||
417 | /* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */ | 818 | /* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */ |
@@ -429,75 +830,75 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, struct gspca_frame *frame, | |||
429 | { | 830 | { |
430 | struct sd *sd = (struct sd *) gspca_dev; | 831 | struct sd *sd = (struct sd *) gspca_dev; |
431 | __u32 this_pts; | 832 | __u32 this_pts; |
432 | int this_fid; | 833 | u16 this_fid; |
433 | int remaining_len = len; | 834 | int remaining_len = len; |
434 | __u8 *next_data = data; | ||
435 | 835 | ||
436 | scan_next: | 836 | do { |
437 | if (remaining_len <= 0) | 837 | len = min(remaining_len, 2040); /*fixme: was 2048*/ |
438 | return; | ||
439 | |||
440 | data = next_data; | ||
441 | len = min(remaining_len, 2048); | ||
442 | remaining_len -= len; | ||
443 | next_data += len; | ||
444 | |||
445 | /* Payloads are prefixed with a UVC-style header. We | ||
446 | consider a frame to start when the FID toggles, or the PTS | ||
447 | changes. A frame ends when EOF is set, and we've received | ||
448 | the correct number of bytes. */ | ||
449 | |||
450 | /* Verify UVC header. Header length is always 12 */ | ||
451 | if (data[0] != 12 || len < 12) { | ||
452 | PDEBUG(D_PACK, "bad header"); | ||
453 | goto discard; | ||
454 | } | ||
455 | |||
456 | /* Check errors */ | ||
457 | if (data[1] & UVC_STREAM_ERR) { | ||
458 | PDEBUG(D_PACK, "payload error"); | ||
459 | goto discard; | ||
460 | } | ||
461 | 838 | ||
462 | /* Extract PTS and FID */ | 839 | /* Payloads are prefixed with a UVC-style header. We |
463 | if (!(data[1] & UVC_STREAM_PTS)) { | 840 | consider a frame to start when the FID toggles, or the PTS |
464 | PDEBUG(D_PACK, "PTS not present"); | 841 | changes. A frame ends when EOF is set, and we've received |
465 | goto discard; | 842 | the correct number of bytes. */ |
466 | } | ||
467 | this_pts = (data[5] << 24) | (data[4] << 16) | (data[3] << 8) | data[2]; | ||
468 | this_fid = (data[1] & UVC_STREAM_FID) ? 1 : 0; | ||
469 | |||
470 | /* If PTS or FID has changed, start a new frame. */ | ||
471 | if (this_pts != sd->last_pts || this_fid != sd->last_fid) { | ||
472 | gspca_frame_add(gspca_dev, FIRST_PACKET, frame, NULL, 0); | ||
473 | sd->last_pts = this_pts; | ||
474 | sd->last_fid = this_fid; | ||
475 | } | ||
476 | 843 | ||
477 | /* Add the data from this payload */ | 844 | /* Verify UVC header. Header length is always 12 */ |
478 | gspca_frame_add(gspca_dev, INTER_PACKET, frame, | 845 | if (data[0] != 12 || len < 12) { |
479 | data + 12, len - 12); | 846 | PDEBUG(D_PACK, "bad header"); |
847 | goto discard; | ||
848 | } | ||
480 | 849 | ||
481 | /* If this packet is marked as EOF, end the frame */ | 850 | /* Check errors */ |
482 | if (data[1] & UVC_STREAM_EOF) { | 851 | if (data[1] & UVC_STREAM_ERR) { |
483 | sd->last_pts = 0; | 852 | PDEBUG(D_PACK, "payload error"); |
853 | goto discard; | ||
854 | } | ||
484 | 855 | ||
485 | if ((frame->data_end - frame->data) != | 856 | /* Extract PTS and FID */ |
486 | (gspca_dev->width * gspca_dev->height * 2)) { | 857 | if (!(data[1] & UVC_STREAM_PTS)) { |
487 | PDEBUG(D_PACK, "short frame"); | 858 | PDEBUG(D_PACK, "PTS not present"); |
488 | goto discard; | 859 | goto discard; |
489 | } | 860 | } |
861 | this_pts = (data[5] << 24) | (data[4] << 16) | ||
862 | | (data[3] << 8) | data[2]; | ||
863 | this_fid = (data[1] & UVC_STREAM_FID) ? 1 : 0; | ||
864 | |||
865 | /* If PTS or FID has changed, start a new frame. */ | ||
866 | if (this_pts != sd->last_pts || this_fid != sd->last_fid) { | ||
867 | gspca_frame_add(gspca_dev, FIRST_PACKET, frame, | ||
868 | NULL, 0); | ||
869 | sd->last_pts = this_pts; | ||
870 | sd->last_fid = this_fid; | ||
871 | } | ||
490 | 872 | ||
491 | gspca_frame_add(gspca_dev, LAST_PACKET, frame, NULL, 0); | 873 | /* Add the data from this payload */ |
492 | } | 874 | gspca_frame_add(gspca_dev, INTER_PACKET, frame, |
875 | data + 12, len - 12); | ||
493 | 876 | ||
494 | /* Done this payload */ | 877 | /* If this packet is marked as EOF, end the frame */ |
495 | goto scan_next; | 878 | if (data[1] & UVC_STREAM_EOF) { |
879 | sd->last_pts = 0; | ||
880 | |||
881 | if (frame->data_end - frame->data != | ||
882 | gspca_dev->width * gspca_dev->height * 2) { | ||
883 | PDEBUG(D_PACK, "short frame"); | ||
884 | goto discard; | ||
885 | } | ||
886 | |||
887 | frame = gspca_frame_add(gspca_dev, LAST_PACKET, frame, | ||
888 | NULL, 0); | ||
889 | } | ||
890 | |||
891 | /* Done this payload */ | ||
892 | goto scan_next; | ||
496 | 893 | ||
497 | discard: | 894 | discard: |
498 | /* Discard data until a new frame starts. */ | 895 | /* Discard data until a new frame starts. */ |
499 | gspca_frame_add(gspca_dev, DISCARD_PACKET, frame, NULL, 0); | 896 | gspca_frame_add(gspca_dev, DISCARD_PACKET, frame, NULL, 0); |
500 | goto scan_next; | 897 | |
898 | scan_next: | ||
899 | remaining_len -= len; | ||
900 | data += len; | ||
901 | } while (remaining_len > 0); | ||
501 | } | 902 | } |
502 | 903 | ||
503 | /* get stream parameters (framerate) */ | 904 | /* get stream parameters (framerate) */ |
@@ -556,9 +957,8 @@ static const struct sd_desc sd_desc = { | |||
556 | 957 | ||
557 | /* -- module initialisation -- */ | 958 | /* -- module initialisation -- */ |
558 | static const __devinitdata struct usb_device_id device_table[] = { | 959 | static const __devinitdata struct usb_device_id device_table[] = { |
559 | {USB_DEVICE(0x06f8, 0x3002)}, /* Hercules Blog Webcam */ | 960 | {USB_DEVICE(0x06f8, 0x3003), .driver_info = SENSOR_OV965X}, |
560 | {USB_DEVICE(0x06f8, 0x3003)}, /* Hercules Dualpix HD Weblog */ | 961 | {USB_DEVICE(0x1415, 0x2000), .driver_info = SENSOR_OV772X}, |
561 | {USB_DEVICE(0x1415, 0x2000)}, /* Sony HD Eye for PS3 (SLEH 00201) */ | ||
562 | {} | 962 | {} |
563 | }; | 963 | }; |
564 | 964 | ||
@@ -585,8 +985,10 @@ static struct usb_driver sd_driver = { | |||
585 | /* -- module insert / remove -- */ | 985 | /* -- module insert / remove -- */ |
586 | static int __init sd_mod_init(void) | 986 | static int __init sd_mod_init(void) |
587 | { | 987 | { |
588 | if (usb_register(&sd_driver) < 0) | 988 | int ret; |
589 | return -1; | 989 | ret = usb_register(&sd_driver); |
990 | if (ret < 0) | ||
991 | return ret; | ||
590 | PDEBUG(D_PROBE, "registered"); | 992 | PDEBUG(D_PROBE, "registered"); |
591 | return 0; | 993 | return 0; |
592 | } | 994 | } |