]>
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 | */ | |
23 | ||
24 | #include "cx18-driver.h" | |
25 | #include "cx18-av-core.h" | |
26 | #include "cx18-cards.h" | |
27 | #include "cx18-ioctl.h" | |
28 | #include "cx18-audio.h" | |
29 | #include "cx18-i2c.h" | |
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 */ | |
66 | case V4L2_CID_BRIGHTNESS: | |
67 | case V4L2_CID_HUE: | |
68 | case V4L2_CID_SATURATION: | |
69 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 70 | if (v4l2_subdev_call(cx->sd_av, core, queryctrl, qctrl)) |
1c1e45d1 HV |
71 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; |
72 | return 0; | |
73 | ||
74 | case V4L2_CID_AUDIO_VOLUME: | |
75 | case V4L2_CID_AUDIO_MUTE: | |
76 | case V4L2_CID_AUDIO_BALANCE: | |
77 | case V4L2_CID_AUDIO_BASS: | |
78 | case V4L2_CID_AUDIO_TREBLE: | |
79 | case V4L2_CID_AUDIO_LOUDNESS: | |
80 | if (cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_QUERYCTRL, qctrl)) | |
81 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; | |
82 | return 0; | |
83 | ||
84 | default: | |
85 | if (cx2341x_ctrl_query(&cx->params, qctrl)) | |
86 | qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; | |
87 | return 0; | |
88 | } | |
89 | strncpy(qctrl->name, name, sizeof(qctrl->name) - 1); | |
90 | qctrl->name[sizeof(qctrl->name) - 1] = 0; | |
91 | return 0; | |
92 | } | |
93 | ||
3b6fe58f | 94 | int cx18_querymenu(struct file *file, void *fh, struct v4l2_querymenu *qmenu) |
1c1e45d1 | 95 | { |
e0e31cdb | 96 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; |
1c1e45d1 HV |
97 | struct v4l2_queryctrl qctrl; |
98 | ||
99 | qctrl.id = qmenu->id; | |
3b6fe58f | 100 | cx18_queryctrl(file, fh, &qctrl); |
e0e31cdb HV |
101 | return v4l2_ctrl_query_menu(qmenu, &qctrl, |
102 | cx2341x_ctrl_get_menu(&cx->params, qmenu->id)); | |
1c1e45d1 HV |
103 | } |
104 | ||
adb65bc7 HV |
105 | static int cx18_try_ctrl(struct file *file, void *fh, |
106 | struct v4l2_ext_control *vctrl) | |
1c1e45d1 | 107 | { |
adb65bc7 HV |
108 | struct v4l2_queryctrl qctrl; |
109 | const char **menu_items = NULL; | |
110 | int err; | |
1c1e45d1 | 111 | |
adb65bc7 HV |
112 | qctrl.id = vctrl->id; |
113 | err = cx18_queryctrl(file, fh, &qctrl); | |
114 | if (err) | |
115 | return err; | |
116 | if (qctrl.type == V4L2_CTRL_TYPE_MENU) | |
117 | menu_items = v4l2_ctrl_get_menu(qctrl.id); | |
118 | return v4l2_ctrl_check(vctrl, &qctrl, menu_items); | |
119 | } | |
3b6fe58f | 120 | |
adb65bc7 HV |
121 | static int cx18_s_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) |
122 | { | |
1c1e45d1 HV |
123 | switch (vctrl->id) { |
124 | /* Standard V4L2 controls */ | |
125 | case V4L2_CID_BRIGHTNESS: | |
126 | case V4L2_CID_HUE: | |
127 | case V4L2_CID_SATURATION: | |
128 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 129 | return v4l2_subdev_call(cx->sd_av, core, s_ctrl, vctrl); |
1c1e45d1 HV |
130 | |
131 | case V4L2_CID_AUDIO_VOLUME: | |
132 | case V4L2_CID_AUDIO_MUTE: | |
133 | case V4L2_CID_AUDIO_BALANCE: | |
134 | case V4L2_CID_AUDIO_BASS: | |
135 | case V4L2_CID_AUDIO_TREBLE: | |
136 | case V4L2_CID_AUDIO_LOUDNESS: | |
137 | return cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_S_CTRL, vctrl); | |
138 | ||
139 | default: | |
37f89f95 | 140 | CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); |
1c1e45d1 HV |
141 | return -EINVAL; |
142 | } | |
143 | return 0; | |
144 | } | |
145 | ||
adb65bc7 | 146 | static int cx18_g_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) |
1c1e45d1 | 147 | { |
1c1e45d1 HV |
148 | switch (vctrl->id) { |
149 | /* Standard V4L2 controls */ | |
150 | case V4L2_CID_BRIGHTNESS: | |
151 | case V4L2_CID_HUE: | |
152 | case V4L2_CID_SATURATION: | |
153 | case V4L2_CID_CONTRAST: | |
fa3e7036 | 154 | return v4l2_subdev_call(cx->sd_av, core, g_ctrl, vctrl); |
1c1e45d1 HV |
155 | |
156 | case V4L2_CID_AUDIO_VOLUME: | |
157 | case V4L2_CID_AUDIO_MUTE: | |
158 | case V4L2_CID_AUDIO_BALANCE: | |
159 | case V4L2_CID_AUDIO_BASS: | |
160 | case V4L2_CID_AUDIO_TREBLE: | |
161 | case V4L2_CID_AUDIO_LOUDNESS: | |
162 | return cx18_i2c_hw(cx, cx->card->hw_audio_ctrl, VIDIOC_G_CTRL, vctrl); | |
163 | default: | |
37f89f95 | 164 | CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); |
1c1e45d1 HV |
165 | return -EINVAL; |
166 | } | |
167 | return 0; | |
168 | } | |
169 | ||
170 | static int cx18_setup_vbi_fmt(struct cx18 *cx, enum v4l2_mpeg_stream_vbi_fmt fmt) | |
171 | { | |
172 | if (!(cx->v4l2_cap & V4L2_CAP_SLICED_VBI_CAPTURE)) | |
173 | return -EINVAL; | |
31554ae5 | 174 | if (atomic_read(&cx->ana_capturing) > 0) |
1c1e45d1 HV |
175 | return -EBUSY; |
176 | ||
177 | /* First try to allocate sliced VBI buffers if needed. */ | |
178 | if (fmt && cx->vbi.sliced_mpeg_data[0] == NULL) { | |
179 | int i; | |
180 | ||
181 | for (i = 0; i < CX18_VBI_FRAMES; i++) { | |
302df970 AW |
182 | cx->vbi.sliced_mpeg_data[i] = |
183 | kmalloc(CX18_SLICED_MPEG_DATA_BUFSZ, GFP_KERNEL); | |
1c1e45d1 HV |
184 | if (cx->vbi.sliced_mpeg_data[i] == NULL) { |
185 | while (--i >= 0) { | |
186 | kfree(cx->vbi.sliced_mpeg_data[i]); | |
187 | cx->vbi.sliced_mpeg_data[i] = NULL; | |
188 | } | |
189 | return -ENOMEM; | |
190 | } | |
191 | } | |
192 | } | |
193 | ||
194 | cx->vbi.insert_mpeg = fmt; | |
195 | ||
196 | if (cx->vbi.insert_mpeg == 0) | |
197 | return 0; | |
198 | /* Need sliced data for mpeg insertion */ | |
aed6abd6 | 199 | if (cx18_get_service_set(cx->vbi.sliced_in) == 0) { |
1c1e45d1 HV |
200 | if (cx->is_60hz) |
201 | cx->vbi.sliced_in->service_set = V4L2_SLICED_CAPTION_525; | |
202 | else | |
203 | cx->vbi.sliced_in->service_set = V4L2_SLICED_WSS_625; | |
aed6abd6 | 204 | cx18_expand_service_set(cx->vbi.sliced_in, cx->is_50hz); |
1c1e45d1 HV |
205 | } |
206 | return 0; | |
207 | } | |
208 | ||
3b6fe58f | 209 | int cx18_g_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
1c1e45d1 | 210 | { |
3b6fe58f | 211 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; |
1c1e45d1 HV |
212 | struct v4l2_control ctrl; |
213 | ||
3b6fe58f AW |
214 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
215 | int i; | |
216 | int err = 0; | |
217 | ||
218 | for (i = 0; i < c->count; i++) { | |
219 | ctrl.id = c->controls[i].id; | |
220 | ctrl.value = c->controls[i].value; | |
adb65bc7 | 221 | err = cx18_g_ctrl(cx, &ctrl); |
3b6fe58f AW |
222 | c->controls[i].value = ctrl.value; |
223 | if (err) { | |
224 | c->error_idx = i; | |
225 | break; | |
1c1e45d1 | 226 | } |
1c1e45d1 | 227 | } |
3b6fe58f AW |
228 | return err; |
229 | } | |
3b6fe58f AW |
230 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) |
231 | return cx2341x_ext_ctrls(&cx->params, 0, c, VIDIOC_G_EXT_CTRLS); | |
232 | return -EINVAL; | |
233 | } | |
1c1e45d1 | 234 | |
3b6fe58f AW |
235 | int cx18_s_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
236 | { | |
237 | struct cx18_open_id *id = fh; | |
238 | struct cx18 *cx = id->cx; | |
239 | int ret; | |
240 | struct v4l2_control ctrl; | |
1c1e45d1 | 241 | |
3b6fe58f AW |
242 | ret = v4l2_prio_check(&cx->prio, &id->prio); |
243 | if (ret) | |
244 | return ret; | |
1c1e45d1 | 245 | |
3b6fe58f AW |
246 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
247 | int i; | |
248 | int err = 0; | |
249 | ||
250 | for (i = 0; i < c->count; i++) { | |
251 | ctrl.id = c->controls[i].id; | |
252 | ctrl.value = c->controls[i].value; | |
adb65bc7 | 253 | err = cx18_s_ctrl(cx, &ctrl); |
3b6fe58f AW |
254 | c->controls[i].value = ctrl.value; |
255 | if (err) { | |
256 | c->error_idx = i; | |
257 | break; | |
1c1e45d1 | 258 | } |
1c1e45d1 | 259 | } |
3b6fe58f | 260 | return err; |
1c1e45d1 | 261 | } |
3b6fe58f | 262 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) { |
50b86bac | 263 | struct cx18_api_func_private priv; |
3b6fe58f AW |
264 | struct cx2341x_mpeg_params p = cx->params; |
265 | int err = cx2341x_ext_ctrls(&p, atomic_read(&cx->ana_capturing), | |
266 | c, VIDIOC_S_EXT_CTRLS); | |
1c1e45d1 | 267 | |
3b6fe58f | 268 | if (err) |
1c1e45d1 | 269 | return err; |
1c1e45d1 | 270 | |
3b6fe58f AW |
271 | if (p.video_encoding != cx->params.video_encoding) { |
272 | int is_mpeg1 = p.video_encoding == | |
273 | V4L2_MPEG_VIDEO_ENCODING_MPEG_1; | |
274 | struct v4l2_format fmt; | |
275 | ||
276 | /* fix videodecoder resolution */ | |
277 | fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
278 | fmt.fmt.pix.width = cx->params.width | |
279 | / (is_mpeg1 ? 2 : 1); | |
280 | fmt.fmt.pix.height = cx->params.height; | |
fa3e7036 | 281 | v4l2_subdev_call(cx->sd_av, video, s_fmt, &fmt); |
3b6fe58f | 282 | } |
50b86bac AW |
283 | priv.cx = cx; |
284 | priv.s = &cx->streams[id->type]; | |
285 | err = cx2341x_update(&priv, cx18_api_func, &cx->params, &p); | |
3b6fe58f AW |
286 | if (!err && cx->params.stream_vbi_fmt != p.stream_vbi_fmt) |
287 | err = cx18_setup_vbi_fmt(cx, p.stream_vbi_fmt); | |
288 | cx->params = p; | |
289 | cx->dualwatch_stereo_mode = p.audio_properties & 0x0300; | |
290 | cx18_audio_set_audio_clock_freq(cx, p.audio_properties & 0x03); | |
291 | return err; | |
1c1e45d1 | 292 | } |
3b6fe58f AW |
293 | return -EINVAL; |
294 | } | |
1c1e45d1 | 295 | |
3b6fe58f AW |
296 | int cx18_try_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) |
297 | { | |
298 | struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; | |
299 | ||
adb65bc7 HV |
300 | if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { |
301 | int i; | |
302 | int err = 0; | |
303 | ||
304 | for (i = 0; i < c->count; i++) { | |
305 | err = cx18_try_ctrl(file, fh, &c->controls[i]); | |
306 | if (err) { | |
307 | c->error_idx = i; | |
308 | break; | |
309 | } | |
310 | } | |
311 | return err; | |
312 | } | |
3b6fe58f AW |
313 | if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) |
314 | return cx2341x_ext_ctrls(&cx->params, | |
315 | atomic_read(&cx->ana_capturing), | |
316 | c, VIDIOC_TRY_EXT_CTRLS); | |
317 | return -EINVAL; | |
1c1e45d1 | 318 | } |