]>
Commit | Line | Data |
---|---|---|
1c1e45d1 HV |
1 | /* |
2 | * cx18 ioctl control functions | |
3 | * | |
4 | * Derived from ivtv-controls.c | |
5 | * | |
6 | * Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | |
21 | * 02111-1307 USA | |
22 | */ | |
225aeb1c | 23 | #include <linux/kernel.h> |
5a0e3ad6 | 24 | #include <linux/slab.h> |
1c1e45d1 HV |
25 | |
26 | #include "cx18-driver.h" | |
1c1e45d1 HV |
27 | #include "cx18-cards.h" |
28 | #include "cx18-ioctl.h" | |
29 | #include "cx18-audio.h" | |
1c1e45d1 HV |
30 | #include "cx18-mailbox.h" |
31 | #include "cx18-controls.h" | |
32 | ||
2ba58894 | 33 | /* Must be sorted from low to high control ID! */ |
1c1e45d1 HV |
34 | static const u32 user_ctrls[] = { |
35 | V4L2_CID_USER_CLASS, | |
36 | V4L2_CID_BRIGHTNESS, | |
37 | V4L2_CID_CONTRAST, | |
38 | V4L2_CID_SATURATION, | |
39 | V4L2_CID_HUE, | |
40 | V4L2_CID_AUDIO_VOLUME, | |
41 | V4L2_CID_AUDIO_BALANCE, | |
42 | V4L2_CID_AUDIO_BASS, | |
43 | V4L2_CID_AUDIO_TREBLE, | |
44 | V4L2_CID_AUDIO_MUTE, | |
45 | V4L2_CID_AUDIO_LOUDNESS, | |
46 | 0 | |
47 | }; | |
48 | ||
49 | static const u32 *ctrl_classes[] = { | |
50 | user_ctrls, | |
51 | cx2341x_mpeg_ctrls, | |
52 | NULL | |
53 | }; | |
54 | ||
3b6fe58f | 55 | int cx18_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *qctrl) |
1c1e45d1 | 56 | { |
3b6fe58f | 57 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; |
1c1e45d1 HV |
58 | const char *name; |
59 | ||
1c1e45d1 HV |
60 | qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id); |
61 | if (qctrl->id == 0) | |
62 | return -EINVAL; | |
63 | ||
64 | switch (qctrl->id) { | |
65 | /* Standard V4L2 controls */ | |
c6711c3e HV |
66 | case V4L2_CID_USER_CLASS: |
67 | return v4l2_ctrl_query_fill(qctrl, 0, 0, 0, 0); | |
1c1e45d1 HV |
68 | case V4L2_CID_BRIGHTNESS: |
69 | case V4L2_CID_HUE: | |
70 | case V4L2_CID_SATURATION: | |
71 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 72 | if (v4l2_subdev_call(cx->sd_av, core, queryctrl, qctrl)) |
1c1e45d1 HV |
73 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; |
74 | return 0; | |
75 | ||
76 | case V4L2_CID_AUDIO_VOLUME: | |
77 | case V4L2_CID_AUDIO_MUTE: | |
78 | case V4L2_CID_AUDIO_BALANCE: | |
79 | case V4L2_CID_AUDIO_BASS: | |
80 | case V4L2_CID_AUDIO_TREBLE: | |
81 | case V4L2_CID_AUDIO_LOUDNESS: | |
ff2a2001 | 82 | if (v4l2_subdev_call(cx->sd_av, core, queryctrl, qctrl)) |
1c1e45d1 HV |
83 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; |
84 | return 0; | |
85 | ||
86 | default: | |
87 | if (cx2341x_ctrl_query(&cx->params, qctrl)) | |
88 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; | |
89 | return 0; | |
90 | } | |
91 | strncpy(qctrl->name, name, sizeof(qctrl->name) - 1); | |
92 | qctrl->name[sizeof(qctrl->name) - 1] = 0; | |
93 | return 0; | |
94 | } | |
95 | ||
3b6fe58f | 96 | int cx18_querymenu(struct file *file, void *fh, struct v4l2_querymenu *qmenu) |
1c1e45d1 | 97 | { |
e0e31cdb | 98 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; |
1c1e45d1 HV |
99 | struct v4l2_queryctrl qctrl; |
100 | ||
101 | qctrl.id = qmenu->id; | |
3b6fe58f | 102 | cx18_queryctrl(file, fh, &qctrl); |
e0e31cdb HV |
103 | return v4l2_ctrl_query_menu(qmenu, &qctrl, |
104 | cx2341x_ctrl_get_menu(&cx->params, qmenu->id)); | |
1c1e45d1 HV |
105 | } |
106 | ||
adb65bc7 HV |
107 | static int cx18_try_ctrl(struct file *file, void *fh, |
108 | struct v4l2_ext_control *vctrl) | |
1c1e45d1 | 109 | { |
adb65bc7 HV |
110 | struct v4l2_queryctrl qctrl; |
111 | const char **menu_items = NULL; | |
112 | int err; | |
1c1e45d1 | 113 | |
adb65bc7 HV |
114 | qctrl.id = vctrl->id; |
115 | err = cx18_queryctrl(file, fh, &qctrl); | |
116 | if (err) | |
117 | return err; | |
118 | if (qctrl.type == V4L2_CTRL_TYPE_MENU) | |
119 | menu_items = v4l2_ctrl_get_menu(qctrl.id); | |
120 | return v4l2_ctrl_check(vctrl, &qctrl, menu_items); | |
121 | } | |
3b6fe58f | 122 | |
adb65bc7 HV |
123 | static int cx18_s_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) |
124 | { | |
1c1e45d1 HV |
125 | switch (vctrl->id) { |
126 | /* Standard V4L2 controls */ | |
127 | case V4L2_CID_BRIGHTNESS: | |
128 | case V4L2_CID_HUE: | |
129 | case V4L2_CID_SATURATION: | |
130 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 131 | return v4l2_subdev_call(cx->sd_av, core, s_ctrl, vctrl); |
1c1e45d1 HV |
132 | |
133 | case V4L2_CID_AUDIO_VOLUME: | |
134 | case V4L2_CID_AUDIO_MUTE: | |
135 | case V4L2_CID_AUDIO_BALANCE: | |
136 | case V4L2_CID_AUDIO_BASS: | |
137 | case V4L2_CID_AUDIO_TREBLE: | |
138 | case V4L2_CID_AUDIO_LOUDNESS: | |
ff2a2001 | 139 | return v4l2_subdev_call(cx->sd_av, core, s_ctrl, vctrl); |
1c1e45d1 HV |
140 | |
141 | default: | |
37f89f95 | 142 | CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); |
1c1e45d1 HV |
143 | return -EINVAL; |
144 | } | |
145 | return 0; | |
146 | } | |
147 | ||
adb65bc7 | 148 | static int cx18_g_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) |
1c1e45d1 | 149 | { |
1c1e45d1 HV |
150 | switch (vctrl->id) { |
151 | /* Standard V4L2 controls */ | |
152 | case V4L2_CID_BRIGHTNESS: | |
153 | case V4L2_CID_HUE: | |
154 | case V4L2_CID_SATURATION: | |
155 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 156 | return v4l2_subdev_call(cx->sd_av, core, g_ctrl, vctrl); |
1c1e45d1 HV |
157 | |
158 | case V4L2_CID_AUDIO_VOLUME: | |
159 | case V4L2_CID_AUDIO_MUTE: | |
160 | case V4L2_CID_AUDIO_BALANCE: | |
161 | case V4L2_CID_AUDIO_BASS: | |
162 | case V4L2_CID_AUDIO_TREBLE: | |
163 | case V4L2_CID_AUDIO_LOUDNESS: | |
ff2a2001 AW |
164 | return v4l2_subdev_call(cx->sd_av, core, g_ctrl, vctrl); |
165 | ||
1c1e45d1 | 166 | default: |
37f89f95 | 167 | CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); |
1c1e45d1 HV |
168 | return -EINVAL; |
169 | } | |
170 | return 0; | |
171 | } | |
172 | ||
49a53750 AW |
173 | static int cx18_setup_vbi_fmt(struct cx18 *cx, |
174 | enum v4l2_mpeg_stream_vbi_fmt fmt, | |
175 | enum v4l2_mpeg_stream_type type) | |
1c1e45d1 HV |
176 | { |
177 | if (!(cx->v4l2_cap & V4L2_CAP_SLICED_VBI_CAPTURE)) | |
178 | return -EINVAL; | |
31554ae5 | 179 | if (atomic_read(&cx->ana_capturing) > 0) |
1c1e45d1 HV |
180 | return -EBUSY; |
181 | ||
49a53750 | 182 | if (fmt != V4L2_MPEG_STREAM_VBI_FMT_IVTV || |
0c629252 AW |
183 | !(type == V4L2_MPEG_STREAM_TYPE_MPEG2_PS || |
184 | type == V4L2_MPEG_STREAM_TYPE_MPEG2_DVD || | |
185 | type == V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD)) { | |
186 | /* Only IVTV fmt VBI insertion & only MPEG-2 PS type streams */ | |
49a53750 AW |
187 | cx->vbi.insert_mpeg = V4L2_MPEG_STREAM_VBI_FMT_NONE; |
188 | CX18_DEBUG_INFO("disabled insertion of sliced VBI data into " | |
189 | "the MPEG stream\n"); | |
190 | return 0; | |
191 | } | |
192 | ||
193 | /* Allocate sliced VBI buffers if needed. */ | |
194 | if (cx->vbi.sliced_mpeg_data[0] == NULL) { | |
1c1e45d1 HV |
195 | int i; |
196 | ||
197 | for (i = 0; i < CX18_VBI_FRAMES; i++) { | |
302df970 AW |
198 | cx->vbi.sliced_mpeg_data[i] = |
199 | kmalloc(CX18_SLICED_MPEG_DATA_BUFSZ, GFP_KERNEL); | |
1c1e45d1 HV |
200 | if (cx->vbi.sliced_mpeg_data[i] == NULL) { |
201 | while (--i >= 0) { | |
202 | kfree(cx->vbi.sliced_mpeg_data[i]); | |
203 | cx->vbi.sliced_mpeg_data[i] = NULL; | |
204 | } | |
49a53750 AW |
205 | cx->vbi.insert_mpeg = |
206 | V4L2_MPEG_STREAM_VBI_FMT_NONE; | |
207 | CX18_WARN("Unable to allocate buffers for " | |
208 | "sliced VBI data insertion\n"); | |
1c1e45d1 HV |
209 | return -ENOMEM; |
210 | } | |
211 | } | |
212 | } | |
213 | ||
214 | cx->vbi.insert_mpeg = fmt; | |
49a53750 AW |
215 | CX18_DEBUG_INFO("enabled insertion of sliced VBI data into the MPEG PS," |
216 | "when sliced VBI is enabled\n"); | |
1c1e45d1 | 217 | |
49a53750 AW |
218 | /* |
219 | * If our current settings have no lines set for capture, store a valid, | |
220 | * default set of service lines to capture, in our current settings. | |
221 | */ | |
aed6abd6 | 222 | if (cx18_get_service_set(cx->vbi.sliced_in) == 0) { |
1c1e45d1 | 223 | if (cx->is_60hz) |
49a53750 AW |
224 | cx->vbi.sliced_in->service_set = |
225 | V4L2_SLICED_CAPTION_525; | |
1c1e45d1 HV |
226 | else |
227 | cx->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625; | |
aed6abd6 | 228 | cx18_expand_service_set(cx->vbi.sliced_in, cx->is_50hz); |
1c1e45d1 HV |
229 | } |
230 | return 0; | |
231 | } | |
232 | ||
3b6fe58f | 233 | int cx18_g_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
1c1e45d1 | 234 | { |
3b6fe58f | 235 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; |
1c1e45d1 HV |
236 | struct v4l2_control ctrl; |
237 | ||
3b6fe58f AW |
238 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
239 | int i; | |
240 | int err = 0; | |
241 | ||
242 | for (i = 0; i < c->count; i++) { | |
243 | ctrl.id = c->controls[i].id; | |
244 | ctrl.value = c->controls[i].value; | |
adb65bc7 | 245 | err = cx18_g_ctrl(cx, &ctrl); |
3b6fe58f AW |
246 | c->controls[i].value = ctrl.value; |
247 | if (err) { | |
248 | c->error_idx = i; | |
249 | break; | |
1c1e45d1 | 250 | } |
1c1e45d1 | 251 | } |
3b6fe58f AW |
252 | return err; |
253 | } | |
3b6fe58f AW |
254 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) |
255 | return cx2341x_ext_ctrls(&cx->params, 0, c, VIDIOC_G_EXT_CTRLS); | |
256 | return -EINVAL; | |
257 | } | |
1c1e45d1 | 258 | |
3b6fe58f AW |
259 | int cx18_s_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
260 | { | |
261 | struct cx18_open_id *id = fh; | |
262 | struct cx18 *cx = id->cx; | |
263 | int ret; | |
264 | struct v4l2_control ctrl; | |
1c1e45d1 | 265 | |
3b6fe58f AW |
266 | ret = v4l2_prio_check(&cx->prio, &id->prio); |
267 | if (ret) | |
268 | return ret; | |
1c1e45d1 | 269 | |
3b6fe58f AW |
270 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
271 | int i; | |
272 | int err = 0; | |
273 | ||
274 | for (i = 0; i < c->count; i++) { | |
275 | ctrl.id = c->controls[i].id; | |
276 | ctrl.value = c->controls[i].value; | |
adb65bc7 | 277 | err = cx18_s_ctrl(cx, &ctrl); |
3b6fe58f AW |
278 | c->controls[i].value = ctrl.value; |
279 | if (err) { | |
280 | c->error_idx = i; | |
281 | break; | |
1c1e45d1 | 282 | } |
1c1e45d1 | 283 | } |
3b6fe58f | 284 | return err; |
1c1e45d1 | 285 | } |
3b6fe58f | 286 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) { |
ff2a2001 | 287 | static u32 freqs[3] = { 44100, 48000, 32000 }; |
50b86bac | 288 | struct cx18_api_func_private priv; |
3b6fe58f AW |
289 | struct cx2341x_mpeg_params p = cx->params; |
290 | int err = cx2341x_ext_ctrls(&p, atomic_read(&cx->ana_capturing), | |
291 | c, VIDIOC_S_EXT_CTRLS); | |
ff2a2001 | 292 | unsigned int idx; |
1c1e45d1 | 293 | |
3b6fe58f | 294 | if (err) |
1c1e45d1 | 295 | return err; |
1c1e45d1 | 296 | |
3b6fe58f AW |
297 | if (p.video_encoding != cx->params.video_encoding) { |
298 | int is_mpeg1 = p.video_encoding == | |
299 | V4L2_MPEG_VIDEO_ENCODING_MPEG_1; | |
300 | struct v4l2_format fmt; | |
301 | ||
302 | /* fix videodecoder resolution */ | |
303 | fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
304 | fmt.fmt.pix.width = cx->params.width | |
305 | / (is_mpeg1 ? 2 : 1); | |
306 | fmt.fmt.pix.height = cx->params.height; | |
fa3e7036 | 307 | v4l2_subdev_call(cx->sd_av, video, s_fmt, &fmt); |
3b6fe58f | 308 | } |
50b86bac AW |
309 | priv.cx = cx; |
310 | priv.s = &cx->streams[id->type]; | |
311 | err = cx2341x_update(&priv, cx18_api_func, &cx->params, &p); | |
49a53750 AW |
312 | if (!err && |
313 | (cx->params.stream_vbi_fmt != p.stream_vbi_fmt || | |
314 | cx->params.stream_type != p.stream_type)) | |
315 | err = cx18_setup_vbi_fmt(cx, p.stream_vbi_fmt, | |
316 | p.stream_type); | |
3b6fe58f AW |
317 | cx->params = p; |
318 | cx->dualwatch_stereo_mode = p.audio_properties & 0x0300; | |
ff2a2001 AW |
319 | idx = p.audio_properties & 0x03; |
320 | /* The audio clock of the digitizer must match the codec sample | |
321 | rate otherwise you get some very strange effects. */ | |
225aeb1c | 322 | if (idx < ARRAY_SIZE(freqs)) |
ff2a2001 | 323 | cx18_call_all(cx, audio, s_clock_freq, freqs[idx]); |
3b6fe58f | 324 | return err; |
1c1e45d1 | 325 | } |
3b6fe58f AW |
326 | return -EINVAL; |
327 | } | |
1c1e45d1 | 328 | |
3b6fe58f AW |
329 | int cx18_try_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
330 | { | |
331 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; | |
332 | ||
adb65bc7 HV |
333 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
334 | int i; | |
335 | int err = 0; | |
336 | ||
337 | for (i = 0; i < c->count; i++) { | |
338 | err = cx18_try_ctrl(file, fh, &c->controls[i]); | |
339 | if (err) { | |
340 | c->error_idx = i; | |
341 | break; | |
342 | } | |
343 | } | |
344 | return err; | |
345 | } | |
3b6fe58f AW |
346 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) |
347 | return cx2341x_ext_ctrls(&cx->params, | |
348 | atomic_read(&cx->ana_capturing), | |
349 | c, VIDIOC_TRY_EXT_CTRLS); | |
350 | return -EINVAL; | |
1c1e45d1 | 351 | } |