1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Linux driver for Philips webcam |
3 | USB and Video4Linux interface part. |
4 | (C) 1999-2004 Nemosoft Unv. |
5 | (C) 2004-2006 Luc Saillard (luc@saillard.org) |
6 | (C) 2011 Hans de Goede <hdegoede@redhat.com> |
7 | |
8 | NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx |
9 | driver and thus may have bugs that are not present in the original version. |
10 | Please send bug reports and support requests to <luc@saillard.org>. |
11 | The decompression routines have been implemented by reverse-engineering the |
12 | Nemosoft binary pwcx module. Caveat emptor. |
13 | |
14 | |
15 | */ |
16 | |
17 | #include <linux/errno.h> |
18 | #include <linux/init.h> |
19 | #include <linux/mm.h> |
20 | #include <linux/module.h> |
21 | #include <linux/poll.h> |
22 | #include <linux/vmalloc.h> |
23 | #include <linux/jiffies.h> |
24 | #include <asm/io.h> |
25 | |
26 | #include "pwc.h" |
27 | |
28 | #define PWC_CID_CUSTOM(ctrl) ((V4L2_CID_USER_BASE | 0xf000) + custom_ ## ctrl) |
29 | |
30 | static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl); |
31 | static int pwc_s_ctrl(struct v4l2_ctrl *ctrl); |
32 | |
33 | static const struct v4l2_ctrl_ops pwc_ctrl_ops = { |
34 | .g_volatile_ctrl = pwc_g_volatile_ctrl, |
35 | .s_ctrl = pwc_s_ctrl, |
36 | }; |
37 | |
38 | enum { awb_indoor, awb_outdoor, awb_fl, awb_manual, awb_auto }; |
39 | enum { custom_autocontour, custom_contour, custom_noise_reduction, |
40 | custom_awb_speed, custom_awb_delay, |
41 | custom_save_user, custom_restore_user, custom_restore_factory }; |
42 | |
43 | static const char * const [] = { |
44 | "Indoor (Incandescant Lighting) Mode" , |
45 | "Outdoor (Sunlight) Mode" , |
46 | "Indoor (Fluorescent Lighting) Mode" , |
47 | "Manual Mode" , |
48 | "Auto Mode" , |
49 | NULL |
50 | }; |
51 | |
52 | static const struct v4l2_ctrl_config pwc_auto_white_balance_cfg = { |
53 | .ops = &pwc_ctrl_ops, |
54 | .id = V4L2_CID_AUTO_WHITE_BALANCE, |
55 | .type = V4L2_CTRL_TYPE_MENU, |
56 | .max = awb_auto, |
57 | .qmenu = pwc_auto_whitebal_qmenu, |
58 | }; |
59 | |
60 | static const struct v4l2_ctrl_config pwc_autocontour_cfg = { |
61 | .ops = &pwc_ctrl_ops, |
62 | .id = PWC_CID_CUSTOM(autocontour), |
63 | .type = V4L2_CTRL_TYPE_BOOLEAN, |
64 | .name = "Auto contour" , |
65 | .min = 0, |
66 | .max = 1, |
67 | .step = 1, |
68 | }; |
69 | |
70 | static const struct v4l2_ctrl_config pwc_contour_cfg = { |
71 | .ops = &pwc_ctrl_ops, |
72 | .id = PWC_CID_CUSTOM(contour), |
73 | .type = V4L2_CTRL_TYPE_INTEGER, |
74 | .name = "Contour" , |
75 | .flags = V4L2_CTRL_FLAG_SLIDER, |
76 | .min = 0, |
77 | .max = 63, |
78 | .step = 1, |
79 | }; |
80 | |
81 | static const struct v4l2_ctrl_config pwc_backlight_cfg = { |
82 | .ops = &pwc_ctrl_ops, |
83 | .id = V4L2_CID_BACKLIGHT_COMPENSATION, |
84 | .type = V4L2_CTRL_TYPE_BOOLEAN, |
85 | .min = 0, |
86 | .max = 1, |
87 | .step = 1, |
88 | }; |
89 | |
90 | static const struct v4l2_ctrl_config pwc_flicker_cfg = { |
91 | .ops = &pwc_ctrl_ops, |
92 | .id = V4L2_CID_BAND_STOP_FILTER, |
93 | .type = V4L2_CTRL_TYPE_BOOLEAN, |
94 | .min = 0, |
95 | .max = 1, |
96 | .step = 1, |
97 | }; |
98 | |
99 | static const struct v4l2_ctrl_config pwc_noise_reduction_cfg = { |
100 | .ops = &pwc_ctrl_ops, |
101 | .id = PWC_CID_CUSTOM(noise_reduction), |
102 | .type = V4L2_CTRL_TYPE_INTEGER, |
103 | .name = "Dynamic Noise Reduction" , |
104 | .min = 0, |
105 | .max = 3, |
106 | .step = 1, |
107 | }; |
108 | |
109 | static const struct v4l2_ctrl_config pwc_save_user_cfg = { |
110 | .ops = &pwc_ctrl_ops, |
111 | .id = PWC_CID_CUSTOM(save_user), |
112 | .type = V4L2_CTRL_TYPE_BUTTON, |
113 | .name = "Save User Settings" , |
114 | }; |
115 | |
116 | static const struct v4l2_ctrl_config pwc_restore_user_cfg = { |
117 | .ops = &pwc_ctrl_ops, |
118 | .id = PWC_CID_CUSTOM(restore_user), |
119 | .type = V4L2_CTRL_TYPE_BUTTON, |
120 | .name = "Restore User Settings" , |
121 | }; |
122 | |
123 | static const struct v4l2_ctrl_config pwc_restore_factory_cfg = { |
124 | .ops = &pwc_ctrl_ops, |
125 | .id = PWC_CID_CUSTOM(restore_factory), |
126 | .type = V4L2_CTRL_TYPE_BUTTON, |
127 | .name = "Restore Factory Settings" , |
128 | }; |
129 | |
130 | static const struct v4l2_ctrl_config pwc_awb_speed_cfg = { |
131 | .ops = &pwc_ctrl_ops, |
132 | .id = PWC_CID_CUSTOM(awb_speed), |
133 | .type = V4L2_CTRL_TYPE_INTEGER, |
134 | .name = "Auto White Balance Speed" , |
135 | .min = 1, |
136 | .max = 32, |
137 | .step = 1, |
138 | }; |
139 | |
140 | static const struct v4l2_ctrl_config pwc_awb_delay_cfg = { |
141 | .ops = &pwc_ctrl_ops, |
142 | .id = PWC_CID_CUSTOM(awb_delay), |
143 | .type = V4L2_CTRL_TYPE_INTEGER, |
144 | .name = "Auto White Balance Delay" , |
145 | .min = 0, |
146 | .max = 63, |
147 | .step = 1, |
148 | }; |
149 | |
150 | int pwc_init_controls(struct pwc_device *pdev) |
151 | { |
152 | struct v4l2_ctrl_handler *hdl; |
153 | struct v4l2_ctrl_config cfg; |
154 | int r, def; |
155 | |
156 | hdl = &pdev->ctrl_handler; |
157 | r = v4l2_ctrl_handler_init(hdl, 20); |
158 | if (r) |
159 | return r; |
160 | |
161 | /* Brightness, contrast, saturation, gamma */ |
162 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, BRIGHTNESS_FORMATTER, data: &def); |
163 | if (r || def > 127) |
164 | def = 63; |
165 | pdev->brightness = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
166 | V4L2_CID_BRIGHTNESS, min: 0, max: 127, step: 1, def); |
167 | |
168 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, CONTRAST_FORMATTER, data: &def); |
169 | if (r || def > 63) |
170 | def = 31; |
171 | pdev->contrast = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
172 | V4L2_CID_CONTRAST, min: 0, max: 63, step: 1, def); |
173 | |
174 | if (pdev->type >= 675) { |
175 | if (pdev->type < 730) |
176 | pdev->saturation_fmt = SATURATION_MODE_FORMATTER2; |
177 | else |
178 | pdev->saturation_fmt = SATURATION_MODE_FORMATTER1; |
179 | r = pwc_get_s8_ctrl(pdev, GET_CHROM_CTL, value: pdev->saturation_fmt, |
180 | data: &def); |
181 | if (r || def < -100 || def > 100) |
182 | def = 0; |
183 | pdev->saturation = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
184 | V4L2_CID_SATURATION, min: -100, max: 100, step: 1, def); |
185 | } |
186 | |
187 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, GAMMA_FORMATTER, data: &def); |
188 | if (r || def > 31) |
189 | def = 15; |
190 | pdev->gamma = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
191 | V4L2_CID_GAMMA, min: 0, max: 31, step: 1, def); |
192 | |
193 | /* auto white balance, red gain, blue gain */ |
194 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, WB_MODE_FORMATTER, data: &def); |
195 | if (r || def > awb_auto) |
196 | def = awb_auto; |
197 | cfg = pwc_auto_white_balance_cfg; |
198 | cfg.name = v4l2_ctrl_get_name(id: cfg.id); |
199 | cfg.def = def; |
200 | pdev->auto_white_balance = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
201 | /* check auto controls to avoid NULL deref in v4l2_ctrl_auto_cluster */ |
202 | if (!pdev->auto_white_balance) |
203 | return hdl->error; |
204 | |
205 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, |
206 | PRESET_MANUAL_RED_GAIN_FORMATTER, data: &def); |
207 | if (r) |
208 | def = 127; |
209 | pdev->red_balance = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
210 | V4L2_CID_RED_BALANCE, min: 0, max: 255, step: 1, def); |
211 | |
212 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, |
213 | PRESET_MANUAL_BLUE_GAIN_FORMATTER, data: &def); |
214 | if (r) |
215 | def = 127; |
216 | pdev->blue_balance = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
217 | V4L2_CID_BLUE_BALANCE, min: 0, max: 255, step: 1, def); |
218 | |
219 | v4l2_ctrl_auto_cluster(ncontrols: 3, controls: &pdev->auto_white_balance, manual_val: awb_manual, set_volatile: true); |
220 | |
221 | /* autogain, gain */ |
222 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, AGC_MODE_FORMATTER, data: &def); |
223 | if (r || (def != 0 && def != 0xff)) |
224 | def = 0; |
225 | /* Note a register value if 0 means auto gain is on */ |
226 | pdev->autogain = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
227 | V4L2_CID_AUTOGAIN, min: 0, max: 1, step: 1, def: def == 0); |
228 | if (!pdev->autogain) |
229 | return hdl->error; |
230 | |
231 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, PRESET_AGC_FORMATTER, data: &def); |
232 | if (r || def > 63) |
233 | def = 31; |
234 | pdev->gain = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
235 | V4L2_CID_GAIN, min: 0, max: 63, step: 1, def); |
236 | |
237 | /* auto exposure, exposure */ |
238 | if (DEVICE_USE_CODEC2(pdev->type)) { |
239 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, SHUTTER_MODE_FORMATTER, |
240 | data: &def); |
241 | if (r || (def != 0 && def != 0xff)) |
242 | def = 0; |
243 | /* |
244 | * def = 0 auto, def = ff manual |
245 | * menu idx 0 = auto, idx 1 = manual |
246 | */ |
247 | pdev->exposure_auto = v4l2_ctrl_new_std_menu(hdl, |
248 | ops: &pwc_ctrl_ops, |
249 | V4L2_CID_EXPOSURE_AUTO, |
250 | max: 1, mask: 0, def: def != 0); |
251 | if (!pdev->exposure_auto) |
252 | return hdl->error; |
253 | |
254 | /* GET_LUM_CTL, PRESET_SHUTTER_FORMATTER is unreliable */ |
255 | r = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL, |
256 | READ_SHUTTER_FORMATTER, dat: &def); |
257 | if (r || def > 655) |
258 | def = 655; |
259 | pdev->exposure = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
260 | V4L2_CID_EXPOSURE, min: 0, max: 655, step: 1, def); |
261 | /* CODEC2: separate auto gain & auto exposure */ |
262 | v4l2_ctrl_auto_cluster(ncontrols: 2, controls: &pdev->autogain, manual_val: 0, set_volatile: true); |
263 | v4l2_ctrl_auto_cluster(ncontrols: 2, controls: &pdev->exposure_auto, |
264 | manual_val: V4L2_EXPOSURE_MANUAL, set_volatile: true); |
265 | } else if (DEVICE_USE_CODEC3(pdev->type)) { |
266 | /* GET_LUM_CTL, PRESET_SHUTTER_FORMATTER is unreliable */ |
267 | r = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL, |
268 | READ_SHUTTER_FORMATTER, dat: &def); |
269 | if (r || def > 255) |
270 | def = 255; |
271 | pdev->exposure = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
272 | V4L2_CID_EXPOSURE, min: 0, max: 255, step: 1, def); |
273 | /* CODEC3: both gain and exposure controlled by autogain */ |
274 | pdev->autogain_expo_cluster[0] = pdev->autogain; |
275 | pdev->autogain_expo_cluster[1] = pdev->gain; |
276 | pdev->autogain_expo_cluster[2] = pdev->exposure; |
277 | v4l2_ctrl_auto_cluster(ncontrols: 3, controls: pdev->autogain_expo_cluster, |
278 | manual_val: 0, set_volatile: true); |
279 | } |
280 | |
281 | /* color / bw setting */ |
282 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, COLOUR_MODE_FORMATTER, |
283 | data: &def); |
284 | if (r || (def != 0 && def != 0xff)) |
285 | def = 0xff; |
286 | /* def = 0 bw, def = ff color, menu idx 0 = color, idx 1 = bw */ |
287 | pdev->colorfx = v4l2_ctrl_new_std_menu(hdl, ops: &pwc_ctrl_ops, |
288 | V4L2_CID_COLORFX, max: 1, mask: 0, def: def == 0); |
289 | |
290 | /* autocontour, contour */ |
291 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, AUTO_CONTOUR_FORMATTER, data: &def); |
292 | if (r || (def != 0 && def != 0xff)) |
293 | def = 0; |
294 | cfg = pwc_autocontour_cfg; |
295 | cfg.def = def == 0; |
296 | pdev->autocontour = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
297 | if (!pdev->autocontour) |
298 | return hdl->error; |
299 | |
300 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, PRESET_CONTOUR_FORMATTER, data: &def); |
301 | if (r || def > 63) |
302 | def = 31; |
303 | cfg = pwc_contour_cfg; |
304 | cfg.def = def; |
305 | pdev->contour = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
306 | |
307 | v4l2_ctrl_auto_cluster(ncontrols: 2, controls: &pdev->autocontour, manual_val: 0, set_volatile: false); |
308 | |
309 | /* backlight */ |
310 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, |
311 | BACK_LIGHT_COMPENSATION_FORMATTER, data: &def); |
312 | if (r || (def != 0 && def != 0xff)) |
313 | def = 0; |
314 | cfg = pwc_backlight_cfg; |
315 | cfg.name = v4l2_ctrl_get_name(id: cfg.id); |
316 | cfg.def = def == 0; |
317 | pdev->backlight = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
318 | |
319 | /* flikker rediction */ |
320 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, |
321 | FLICKERLESS_MODE_FORMATTER, data: &def); |
322 | if (r || (def != 0 && def != 0xff)) |
323 | def = 0; |
324 | cfg = pwc_flicker_cfg; |
325 | cfg.name = v4l2_ctrl_get_name(id: cfg.id); |
326 | cfg.def = def == 0; |
327 | pdev->flicker = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
328 | |
329 | /* Dynamic noise reduction */ |
330 | r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, |
331 | DYNAMIC_NOISE_CONTROL_FORMATTER, data: &def); |
332 | if (r || def > 3) |
333 | def = 2; |
334 | cfg = pwc_noise_reduction_cfg; |
335 | cfg.def = def; |
336 | pdev->noise_reduction = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
337 | |
338 | /* Save / Restore User / Factory Settings */ |
339 | pdev->save_user = v4l2_ctrl_new_custom(hdl, cfg: &pwc_save_user_cfg, NULL); |
340 | pdev->restore_user = v4l2_ctrl_new_custom(hdl, cfg: &pwc_restore_user_cfg, |
341 | NULL); |
342 | if (pdev->restore_user) |
343 | pdev->restore_user->flags |= V4L2_CTRL_FLAG_UPDATE; |
344 | pdev->restore_factory = v4l2_ctrl_new_custom(hdl, |
345 | cfg: &pwc_restore_factory_cfg, |
346 | NULL); |
347 | if (pdev->restore_factory) |
348 | pdev->restore_factory->flags |= V4L2_CTRL_FLAG_UPDATE; |
349 | |
350 | /* Auto White Balance speed & delay */ |
351 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, |
352 | AWB_CONTROL_SPEED_FORMATTER, data: &def); |
353 | if (r || def < 1 || def > 32) |
354 | def = 1; |
355 | cfg = pwc_awb_speed_cfg; |
356 | cfg.def = def; |
357 | pdev->awb_speed = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
358 | |
359 | r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, |
360 | AWB_CONTROL_DELAY_FORMATTER, data: &def); |
361 | if (r || def > 63) |
362 | def = 0; |
363 | cfg = pwc_awb_delay_cfg; |
364 | cfg.def = def; |
365 | pdev->awb_delay = v4l2_ctrl_new_custom(hdl, cfg: &cfg, NULL); |
366 | |
367 | if (!(pdev->features & FEATURE_MOTOR_PANTILT)) |
368 | return hdl->error; |
369 | |
370 | /* Motor pan / tilt / reset */ |
371 | pdev->motor_pan = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
372 | V4L2_CID_PAN_RELATIVE, min: -4480, max: 4480, step: 64, def: 0); |
373 | if (!pdev->motor_pan) |
374 | return hdl->error; |
375 | pdev->motor_tilt = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
376 | V4L2_CID_TILT_RELATIVE, min: -1920, max: 1920, step: 64, def: 0); |
377 | pdev->motor_pan_reset = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
378 | V4L2_CID_PAN_RESET, min: 0, max: 0, step: 0, def: 0); |
379 | pdev->motor_tilt_reset = v4l2_ctrl_new_std(hdl, ops: &pwc_ctrl_ops, |
380 | V4L2_CID_TILT_RESET, min: 0, max: 0, step: 0, def: 0); |
381 | v4l2_ctrl_cluster(ncontrols: 4, controls: &pdev->motor_pan); |
382 | |
383 | return hdl->error; |
384 | } |
385 | |
386 | static void pwc_vidioc_fill_fmt(struct v4l2_format *f, |
387 | int width, int height, u32 pixfmt) |
388 | { |
389 | memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format)); |
390 | f->fmt.pix.width = width; |
391 | f->fmt.pix.height = height; |
392 | f->fmt.pix.field = V4L2_FIELD_NONE; |
393 | f->fmt.pix.pixelformat = pixfmt; |
394 | f->fmt.pix.bytesperline = f->fmt.pix.width; |
395 | f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.width * 3 / 2; |
396 | f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; |
397 | PWC_DEBUG_IOCTL("pwc_vidioc_fill_fmt() width=%d, height=%d, bytesperline=%d, sizeimage=%d, pixelformat=%c%c%c%c\n" , |
398 | f->fmt.pix.width, |
399 | f->fmt.pix.height, |
400 | f->fmt.pix.bytesperline, |
401 | f->fmt.pix.sizeimage, |
402 | (f->fmt.pix.pixelformat)&255, |
403 | (f->fmt.pix.pixelformat>>8)&255, |
404 | (f->fmt.pix.pixelformat>>16)&255, |
405 | (f->fmt.pix.pixelformat>>24)&255); |
406 | } |
407 | |
408 | /* ioctl(VIDIOC_TRY_FMT) */ |
409 | static int pwc_vidioc_try_fmt(struct pwc_device *pdev, struct v4l2_format *f) |
410 | { |
411 | int size; |
412 | |
413 | if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { |
414 | PWC_DEBUG_IOCTL("Bad video type must be V4L2_BUF_TYPE_VIDEO_CAPTURE\n" ); |
415 | return -EINVAL; |
416 | } |
417 | |
418 | switch (f->fmt.pix.pixelformat) { |
419 | case V4L2_PIX_FMT_YUV420: |
420 | break; |
421 | case V4L2_PIX_FMT_PWC1: |
422 | if (DEVICE_USE_CODEC23(pdev->type)) { |
423 | PWC_DEBUG_IOCTL("codec1 is only supported for old pwc webcam\n" ); |
424 | f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; |
425 | } |
426 | break; |
427 | case V4L2_PIX_FMT_PWC2: |
428 | if (DEVICE_USE_CODEC1(pdev->type)) { |
429 | PWC_DEBUG_IOCTL("codec23 is only supported for new pwc webcam\n" ); |
430 | f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; |
431 | } |
432 | break; |
433 | default: |
434 | PWC_DEBUG_IOCTL("Unsupported pixel format\n" ); |
435 | f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; |
436 | } |
437 | |
438 | size = pwc_get_size(pdev, width: f->fmt.pix.width, height: f->fmt.pix.height); |
439 | pwc_vidioc_fill_fmt(f, |
440 | width: pwc_image_sizes[size][0], |
441 | height: pwc_image_sizes[size][1], |
442 | pixfmt: f->fmt.pix.pixelformat); |
443 | |
444 | return 0; |
445 | } |
446 | |
447 | /* ioctl(VIDIOC_SET_FMT) */ |
448 | |
449 | static int pwc_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
450 | { |
451 | struct pwc_device *pdev = video_drvdata(file); |
452 | int ret, pixelformat, compression = 0; |
453 | |
454 | ret = pwc_vidioc_try_fmt(pdev, f); |
455 | if (ret < 0) |
456 | return ret; |
457 | |
458 | if (vb2_is_busy(q: &pdev->vb_queue)) |
459 | return -EBUSY; |
460 | |
461 | pixelformat = f->fmt.pix.pixelformat; |
462 | |
463 | PWC_DEBUG_IOCTL("Trying to set format to: width=%d height=%d fps=%d format=%c%c%c%c\n" , |
464 | f->fmt.pix.width, f->fmt.pix.height, pdev->vframes, |
465 | (pixelformat)&255, |
466 | (pixelformat>>8)&255, |
467 | (pixelformat>>16)&255, |
468 | (pixelformat>>24)&255); |
469 | |
470 | ret = pwc_set_video_mode(pdev, width: f->fmt.pix.width, height: f->fmt.pix.height, |
471 | pixfmt: pixelformat, frames: 30, compression: &compression, send_to_cam: 0); |
472 | |
473 | PWC_DEBUG_IOCTL("pwc_set_video_mode(), return=%d\n" , ret); |
474 | |
475 | pwc_vidioc_fill_fmt(f, width: pdev->width, height: pdev->height, pixfmt: pdev->pixfmt); |
476 | return ret; |
477 | } |
478 | |
479 | static int pwc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) |
480 | { |
481 | struct pwc_device *pdev = video_drvdata(file); |
482 | |
483 | strscpy(cap->driver, PWC_NAME, sizeof(cap->driver)); |
484 | strscpy(cap->card, pdev->vdev.name, sizeof(cap->card)); |
485 | usb_make_path(dev: pdev->udev, buf: cap->bus_info, size: sizeof(cap->bus_info)); |
486 | return 0; |
487 | } |
488 | |
489 | static int pwc_enum_input(struct file *file, void *fh, struct v4l2_input *i) |
490 | { |
491 | if (i->index) /* Only one INPUT is supported */ |
492 | return -EINVAL; |
493 | |
494 | strscpy(i->name, "Camera" , sizeof(i->name)); |
495 | i->type = V4L2_INPUT_TYPE_CAMERA; |
496 | return 0; |
497 | } |
498 | |
499 | static int pwc_g_input(struct file *file, void *fh, unsigned int *i) |
500 | { |
501 | *i = 0; |
502 | return 0; |
503 | } |
504 | |
505 | static int pwc_s_input(struct file *file, void *fh, unsigned int i) |
506 | { |
507 | return i ? -EINVAL : 0; |
508 | } |
509 | |
510 | static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl) |
511 | { |
512 | struct pwc_device *pdev = |
513 | container_of(ctrl->handler, struct pwc_device, ctrl_handler); |
514 | int ret = 0; |
515 | |
516 | switch (ctrl->id) { |
517 | case V4L2_CID_AUTO_WHITE_BALANCE: |
518 | if (pdev->color_bal_valid && |
519 | (pdev->auto_white_balance->val != awb_auto || |
520 | time_before(jiffies, |
521 | pdev->last_color_bal_update + HZ / 4))) { |
522 | pdev->red_balance->val = pdev->last_red_balance; |
523 | pdev->blue_balance->val = pdev->last_blue_balance; |
524 | break; |
525 | } |
526 | ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL, |
527 | READ_RED_GAIN_FORMATTER, |
528 | data: &pdev->red_balance->val); |
529 | if (ret) |
530 | break; |
531 | ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL, |
532 | READ_BLUE_GAIN_FORMATTER, |
533 | data: &pdev->blue_balance->val); |
534 | if (ret) |
535 | break; |
536 | pdev->last_red_balance = pdev->red_balance->val; |
537 | pdev->last_blue_balance = pdev->blue_balance->val; |
538 | pdev->last_color_bal_update = jiffies; |
539 | pdev->color_bal_valid = true; |
540 | break; |
541 | case V4L2_CID_AUTOGAIN: |
542 | if (pdev->gain_valid && time_before(jiffies, |
543 | pdev->last_gain_update + HZ / 4)) { |
544 | pdev->gain->val = pdev->last_gain; |
545 | break; |
546 | } |
547 | ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL, |
548 | READ_AGC_FORMATTER, data: &pdev->gain->val); |
549 | if (ret) |
550 | break; |
551 | pdev->last_gain = pdev->gain->val; |
552 | pdev->last_gain_update = jiffies; |
553 | pdev->gain_valid = true; |
554 | if (!DEVICE_USE_CODEC3(pdev->type)) |
555 | break; |
556 | /* For CODEC3 where autogain also controls expo */ |
557 | fallthrough; |
558 | case V4L2_CID_EXPOSURE_AUTO: |
559 | if (pdev->exposure_valid && time_before(jiffies, |
560 | pdev->last_exposure_update + HZ / 4)) { |
561 | pdev->exposure->val = pdev->last_exposure; |
562 | break; |
563 | } |
564 | ret = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL, |
565 | READ_SHUTTER_FORMATTER, |
566 | dat: &pdev->exposure->val); |
567 | if (ret) |
568 | break; |
569 | pdev->last_exposure = pdev->exposure->val; |
570 | pdev->last_exposure_update = jiffies; |
571 | pdev->exposure_valid = true; |
572 | break; |
573 | default: |
574 | ret = -EINVAL; |
575 | } |
576 | |
577 | if (ret) |
578 | PWC_ERROR("g_ctrl %s error %d\n" , ctrl->name, ret); |
579 | |
580 | return ret; |
581 | } |
582 | |
583 | static int pwc_set_awb(struct pwc_device *pdev) |
584 | { |
585 | int ret; |
586 | |
587 | if (pdev->auto_white_balance->is_new) { |
588 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
589 | WB_MODE_FORMATTER, |
590 | data: pdev->auto_white_balance->val); |
591 | if (ret) |
592 | return ret; |
593 | |
594 | if (pdev->auto_white_balance->val != awb_manual) |
595 | pdev->color_bal_valid = false; /* Force cache update */ |
596 | |
597 | /* |
598 | * If this is a preset, update our red / blue balance values |
599 | * so that events get generated for the new preset values |
600 | */ |
601 | if (pdev->auto_white_balance->val == awb_indoor || |
602 | pdev->auto_white_balance->val == awb_outdoor || |
603 | pdev->auto_white_balance->val == awb_fl) |
604 | pwc_g_volatile_ctrl(ctrl: pdev->auto_white_balance); |
605 | } |
606 | if (pdev->auto_white_balance->val != awb_manual) |
607 | return 0; |
608 | |
609 | if (pdev->red_balance->is_new) { |
610 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
611 | PRESET_MANUAL_RED_GAIN_FORMATTER, |
612 | data: pdev->red_balance->val); |
613 | if (ret) |
614 | return ret; |
615 | } |
616 | |
617 | if (pdev->blue_balance->is_new) { |
618 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
619 | PRESET_MANUAL_BLUE_GAIN_FORMATTER, |
620 | data: pdev->blue_balance->val); |
621 | if (ret) |
622 | return ret; |
623 | } |
624 | return 0; |
625 | } |
626 | |
627 | /* For CODEC2 models which have separate autogain and auto exposure */ |
628 | static int pwc_set_autogain(struct pwc_device *pdev) |
629 | { |
630 | int ret; |
631 | |
632 | if (pdev->autogain->is_new) { |
633 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
634 | AGC_MODE_FORMATTER, |
635 | data: pdev->autogain->val ? 0 : 0xff); |
636 | if (ret) |
637 | return ret; |
638 | |
639 | if (pdev->autogain->val) |
640 | pdev->gain_valid = false; /* Force cache update */ |
641 | } |
642 | |
643 | if (pdev->autogain->val) |
644 | return 0; |
645 | |
646 | if (pdev->gain->is_new) { |
647 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
648 | PRESET_AGC_FORMATTER, |
649 | data: pdev->gain->val); |
650 | if (ret) |
651 | return ret; |
652 | } |
653 | return 0; |
654 | } |
655 | |
656 | /* For CODEC2 models which have separate autogain and auto exposure */ |
657 | static int pwc_set_exposure_auto(struct pwc_device *pdev) |
658 | { |
659 | int ret; |
660 | int is_auto = pdev->exposure_auto->val == V4L2_EXPOSURE_AUTO; |
661 | |
662 | if (pdev->exposure_auto->is_new) { |
663 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
664 | SHUTTER_MODE_FORMATTER, |
665 | data: is_auto ? 0 : 0xff); |
666 | if (ret) |
667 | return ret; |
668 | |
669 | if (is_auto) |
670 | pdev->exposure_valid = false; /* Force cache update */ |
671 | } |
672 | |
673 | if (is_auto) |
674 | return 0; |
675 | |
676 | if (pdev->exposure->is_new) { |
677 | ret = pwc_set_u16_ctrl(pdev, SET_LUM_CTL, |
678 | PRESET_SHUTTER_FORMATTER, |
679 | data: pdev->exposure->val); |
680 | if (ret) |
681 | return ret; |
682 | } |
683 | return 0; |
684 | } |
685 | |
686 | /* For CODEC3 models which have autogain controlling both gain and exposure */ |
687 | static int pwc_set_autogain_expo(struct pwc_device *pdev) |
688 | { |
689 | int ret; |
690 | |
691 | if (pdev->autogain->is_new) { |
692 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
693 | AGC_MODE_FORMATTER, |
694 | data: pdev->autogain->val ? 0 : 0xff); |
695 | if (ret) |
696 | return ret; |
697 | |
698 | if (pdev->autogain->val) { |
699 | pdev->gain_valid = false; /* Force cache update */ |
700 | pdev->exposure_valid = false; /* Force cache update */ |
701 | } |
702 | } |
703 | |
704 | if (pdev->autogain->val) |
705 | return 0; |
706 | |
707 | if (pdev->gain->is_new) { |
708 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
709 | PRESET_AGC_FORMATTER, |
710 | data: pdev->gain->val); |
711 | if (ret) |
712 | return ret; |
713 | } |
714 | |
715 | if (pdev->exposure->is_new) { |
716 | ret = pwc_set_u16_ctrl(pdev, SET_LUM_CTL, |
717 | PRESET_SHUTTER_FORMATTER, |
718 | data: pdev->exposure->val); |
719 | if (ret) |
720 | return ret; |
721 | } |
722 | return 0; |
723 | } |
724 | |
725 | static int pwc_set_motor(struct pwc_device *pdev) |
726 | { |
727 | int ret; |
728 | |
729 | pdev->ctrl_buf[0] = 0; |
730 | if (pdev->motor_pan_reset->is_new) |
731 | pdev->ctrl_buf[0] |= 0x01; |
732 | if (pdev->motor_tilt_reset->is_new) |
733 | pdev->ctrl_buf[0] |= 0x02; |
734 | if (pdev->motor_pan_reset->is_new || pdev->motor_tilt_reset->is_new) { |
735 | ret = send_control_msg(pdev, SET_MPT_CTL, |
736 | PT_RESET_CONTROL_FORMATTER, |
737 | buf: pdev->ctrl_buf, buflen: 1); |
738 | if (ret < 0) |
739 | return ret; |
740 | } |
741 | |
742 | memset(pdev->ctrl_buf, 0, 4); |
743 | if (pdev->motor_pan->is_new) { |
744 | pdev->ctrl_buf[0] = pdev->motor_pan->val & 0xFF; |
745 | pdev->ctrl_buf[1] = (pdev->motor_pan->val >> 8); |
746 | } |
747 | if (pdev->motor_tilt->is_new) { |
748 | pdev->ctrl_buf[2] = pdev->motor_tilt->val & 0xFF; |
749 | pdev->ctrl_buf[3] = (pdev->motor_tilt->val >> 8); |
750 | } |
751 | if (pdev->motor_pan->is_new || pdev->motor_tilt->is_new) { |
752 | ret = send_control_msg(pdev, SET_MPT_CTL, |
753 | PT_RELATIVE_CONTROL_FORMATTER, |
754 | buf: pdev->ctrl_buf, buflen: 4); |
755 | if (ret < 0) |
756 | return ret; |
757 | } |
758 | |
759 | return 0; |
760 | } |
761 | |
762 | static int pwc_s_ctrl(struct v4l2_ctrl *ctrl) |
763 | { |
764 | struct pwc_device *pdev = |
765 | container_of(ctrl->handler, struct pwc_device, ctrl_handler); |
766 | int ret = 0; |
767 | |
768 | switch (ctrl->id) { |
769 | case V4L2_CID_BRIGHTNESS: |
770 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
771 | BRIGHTNESS_FORMATTER, data: ctrl->val); |
772 | break; |
773 | case V4L2_CID_CONTRAST: |
774 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
775 | CONTRAST_FORMATTER, data: ctrl->val); |
776 | break; |
777 | case V4L2_CID_SATURATION: |
778 | ret = pwc_set_s8_ctrl(pdev, SET_CHROM_CTL, |
779 | value: pdev->saturation_fmt, data: ctrl->val); |
780 | break; |
781 | case V4L2_CID_GAMMA: |
782 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
783 | GAMMA_FORMATTER, data: ctrl->val); |
784 | break; |
785 | case V4L2_CID_AUTO_WHITE_BALANCE: |
786 | ret = pwc_set_awb(pdev); |
787 | break; |
788 | case V4L2_CID_AUTOGAIN: |
789 | if (DEVICE_USE_CODEC2(pdev->type)) |
790 | ret = pwc_set_autogain(pdev); |
791 | else if (DEVICE_USE_CODEC3(pdev->type)) |
792 | ret = pwc_set_autogain_expo(pdev); |
793 | else |
794 | ret = -EINVAL; |
795 | break; |
796 | case V4L2_CID_EXPOSURE_AUTO: |
797 | if (DEVICE_USE_CODEC2(pdev->type)) |
798 | ret = pwc_set_exposure_auto(pdev); |
799 | else |
800 | ret = -EINVAL; |
801 | break; |
802 | case V4L2_CID_COLORFX: |
803 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
804 | COLOUR_MODE_FORMATTER, |
805 | data: ctrl->val ? 0 : 0xff); |
806 | break; |
807 | case PWC_CID_CUSTOM(autocontour): |
808 | if (pdev->autocontour->is_new) { |
809 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
810 | AUTO_CONTOUR_FORMATTER, |
811 | data: pdev->autocontour->val ? 0 : 0xff); |
812 | } |
813 | if (ret == 0 && pdev->contour->is_new) { |
814 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
815 | PRESET_CONTOUR_FORMATTER, |
816 | data: pdev->contour->val); |
817 | } |
818 | break; |
819 | case V4L2_CID_BACKLIGHT_COMPENSATION: |
820 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
821 | BACK_LIGHT_COMPENSATION_FORMATTER, |
822 | data: ctrl->val ? 0 : 0xff); |
823 | break; |
824 | case V4L2_CID_BAND_STOP_FILTER: |
825 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
826 | FLICKERLESS_MODE_FORMATTER, |
827 | data: ctrl->val ? 0 : 0xff); |
828 | break; |
829 | case PWC_CID_CUSTOM(noise_reduction): |
830 | ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL, |
831 | DYNAMIC_NOISE_CONTROL_FORMATTER, |
832 | data: ctrl->val); |
833 | break; |
834 | case PWC_CID_CUSTOM(save_user): |
835 | ret = pwc_button_ctrl(pdev, SAVE_USER_DEFAULTS_FORMATTER); |
836 | break; |
837 | case PWC_CID_CUSTOM(restore_user): |
838 | ret = pwc_button_ctrl(pdev, RESTORE_USER_DEFAULTS_FORMATTER); |
839 | break; |
840 | case PWC_CID_CUSTOM(restore_factory): |
841 | ret = pwc_button_ctrl(pdev, |
842 | RESTORE_FACTORY_DEFAULTS_FORMATTER); |
843 | break; |
844 | case PWC_CID_CUSTOM(awb_speed): |
845 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
846 | AWB_CONTROL_SPEED_FORMATTER, |
847 | data: ctrl->val); |
848 | break; |
849 | case PWC_CID_CUSTOM(awb_delay): |
850 | ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL, |
851 | AWB_CONTROL_DELAY_FORMATTER, |
852 | data: ctrl->val); |
853 | break; |
854 | case V4L2_CID_PAN_RELATIVE: |
855 | ret = pwc_set_motor(pdev); |
856 | break; |
857 | default: |
858 | ret = -EINVAL; |
859 | } |
860 | |
861 | if (ret) |
862 | PWC_ERROR("s_ctrl %s error %d\n" , ctrl->name, ret); |
863 | |
864 | return ret; |
865 | } |
866 | |
867 | static int pwc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) |
868 | { |
869 | struct pwc_device *pdev = video_drvdata(file); |
870 | |
871 | /* We only support two format: the raw format, and YUV */ |
872 | switch (f->index) { |
873 | case 0: |
874 | /* RAW format */ |
875 | f->pixelformat = pdev->type <= 646 ? V4L2_PIX_FMT_PWC1 : V4L2_PIX_FMT_PWC2; |
876 | break; |
877 | case 1: |
878 | f->pixelformat = V4L2_PIX_FMT_YUV420; |
879 | break; |
880 | default: |
881 | return -EINVAL; |
882 | } |
883 | return 0; |
884 | } |
885 | |
886 | static int pwc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
887 | { |
888 | struct pwc_device *pdev = video_drvdata(file); |
889 | |
890 | if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
891 | return -EINVAL; |
892 | |
893 | PWC_DEBUG_IOCTL("ioctl(VIDIOC_G_FMT) return size %dx%d\n" , |
894 | pdev->width, pdev->height); |
895 | pwc_vidioc_fill_fmt(f, width: pdev->width, height: pdev->height, pixfmt: pdev->pixfmt); |
896 | return 0; |
897 | } |
898 | |
899 | static int pwc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
900 | { |
901 | struct pwc_device *pdev = video_drvdata(file); |
902 | |
903 | return pwc_vidioc_try_fmt(pdev, f); |
904 | } |
905 | |
906 | static int pwc_enum_framesizes(struct file *file, void *fh, |
907 | struct v4l2_frmsizeenum *fsize) |
908 | { |
909 | struct pwc_device *pdev = video_drvdata(file); |
910 | unsigned int i = 0, index = fsize->index; |
911 | |
912 | if (fsize->pixel_format == V4L2_PIX_FMT_YUV420 || |
913 | (fsize->pixel_format == V4L2_PIX_FMT_PWC1 && |
914 | DEVICE_USE_CODEC1(pdev->type)) || |
915 | (fsize->pixel_format == V4L2_PIX_FMT_PWC2 && |
916 | DEVICE_USE_CODEC23(pdev->type))) { |
917 | for (i = 0; i < PSZ_MAX; i++) { |
918 | if (!(pdev->image_mask & (1UL << i))) |
919 | continue; |
920 | if (!index--) { |
921 | fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; |
922 | fsize->discrete.width = pwc_image_sizes[i][0]; |
923 | fsize->discrete.height = pwc_image_sizes[i][1]; |
924 | return 0; |
925 | } |
926 | } |
927 | } |
928 | return -EINVAL; |
929 | } |
930 | |
931 | static int pwc_enum_frameintervals(struct file *file, void *fh, |
932 | struct v4l2_frmivalenum *fival) |
933 | { |
934 | struct pwc_device *pdev = video_drvdata(file); |
935 | int size = -1; |
936 | unsigned int i; |
937 | |
938 | for (i = 0; i < PSZ_MAX; i++) { |
939 | if (pwc_image_sizes[i][0] == fival->width && |
940 | pwc_image_sizes[i][1] == fival->height) { |
941 | size = i; |
942 | break; |
943 | } |
944 | } |
945 | |
946 | /* TODO: Support raw format */ |
947 | if (size < 0 || fival->pixel_format != V4L2_PIX_FMT_YUV420) |
948 | return -EINVAL; |
949 | |
950 | i = pwc_get_fps(pdev, index: fival->index, size); |
951 | if (!i) |
952 | return -EINVAL; |
953 | |
954 | fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; |
955 | fival->discrete.numerator = 1; |
956 | fival->discrete.denominator = i; |
957 | |
958 | return 0; |
959 | } |
960 | |
961 | static int pwc_g_parm(struct file *file, void *fh, |
962 | struct v4l2_streamparm *parm) |
963 | { |
964 | struct pwc_device *pdev = video_drvdata(file); |
965 | |
966 | if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
967 | return -EINVAL; |
968 | |
969 | memset(parm, 0, sizeof(*parm)); |
970 | |
971 | parm->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
972 | parm->parm.capture.readbuffers = MIN_FRAMES; |
973 | parm->parm.capture.capability |= V4L2_CAP_TIMEPERFRAME; |
974 | parm->parm.capture.timeperframe.denominator = pdev->vframes; |
975 | parm->parm.capture.timeperframe.numerator = 1; |
976 | |
977 | return 0; |
978 | } |
979 | |
980 | static int pwc_s_parm(struct file *file, void *fh, |
981 | struct v4l2_streamparm *parm) |
982 | { |
983 | struct pwc_device *pdev = video_drvdata(file); |
984 | int compression = 0; |
985 | int ret, fps; |
986 | |
987 | if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
988 | return -EINVAL; |
989 | |
990 | /* If timeperframe == 0, then reset the framerate to the nominal value. |
991 | We pick a high framerate here, and let pwc_set_video_mode() figure |
992 | out the best match. */ |
993 | if (parm->parm.capture.timeperframe.numerator == 0 || |
994 | parm->parm.capture.timeperframe.denominator == 0) |
995 | fps = 30; |
996 | else |
997 | fps = parm->parm.capture.timeperframe.denominator / |
998 | parm->parm.capture.timeperframe.numerator; |
999 | |
1000 | if (vb2_is_busy(q: &pdev->vb_queue)) |
1001 | return -EBUSY; |
1002 | |
1003 | ret = pwc_set_video_mode(pdev, width: pdev->width, height: pdev->height, pixfmt: pdev->pixfmt, |
1004 | frames: fps, compression: &compression, send_to_cam: 0); |
1005 | |
1006 | pwc_g_parm(file, fh, parm); |
1007 | |
1008 | return ret; |
1009 | } |
1010 | |
1011 | const struct v4l2_ioctl_ops pwc_ioctl_ops = { |
1012 | .vidioc_querycap = pwc_querycap, |
1013 | .vidioc_enum_input = pwc_enum_input, |
1014 | .vidioc_g_input = pwc_g_input, |
1015 | .vidioc_s_input = pwc_s_input, |
1016 | .vidioc_enum_fmt_vid_cap = pwc_enum_fmt_vid_cap, |
1017 | .vidioc_g_fmt_vid_cap = pwc_g_fmt_vid_cap, |
1018 | .vidioc_s_fmt_vid_cap = pwc_s_fmt_vid_cap, |
1019 | .vidioc_try_fmt_vid_cap = pwc_try_fmt_vid_cap, |
1020 | .vidioc_reqbufs = vb2_ioctl_reqbufs, |
1021 | .vidioc_querybuf = vb2_ioctl_querybuf, |
1022 | .vidioc_qbuf = vb2_ioctl_qbuf, |
1023 | .vidioc_dqbuf = vb2_ioctl_dqbuf, |
1024 | .vidioc_streamon = vb2_ioctl_streamon, |
1025 | .vidioc_streamoff = vb2_ioctl_streamoff, |
1026 | .vidioc_log_status = v4l2_ctrl_log_status, |
1027 | .vidioc_enum_framesizes = pwc_enum_framesizes, |
1028 | .vidioc_enum_frameintervals = pwc_enum_frameintervals, |
1029 | .vidioc_g_parm = pwc_g_parm, |
1030 | .vidioc_s_parm = pwc_s_parm, |
1031 | .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
1032 | .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
1033 | }; |
1034 | |