]>
Commit | Line | Data |
---|---|---|
e58f3082 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
3e89586a KB |
2 | /* |
3 | * Driver for Analog Devices ADV748X 8 channel analog front end (AFE) receiver | |
4 | * with standard definition processor (SDP) | |
5 | * | |
6 | * Copyright (C) 2017 Renesas Electronics Corp. | |
3e89586a KB |
7 | */ |
8 | ||
9 | #include <linux/delay.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/mutex.h> | |
12 | #include <linux/v4l2-dv-timings.h> | |
13 | ||
14 | #include <media/v4l2-ctrls.h> | |
15 | #include <media/v4l2-device.h> | |
16 | #include <media/v4l2-dv-timings.h> | |
17 | #include <media/v4l2-ioctl.h> | |
18 | ||
19 | #include "adv748x.h" | |
20 | ||
21 | /* ----------------------------------------------------------------------------- | |
22 | * SDP | |
23 | */ | |
24 | ||
25 | #define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM 0x0 | |
26 | #define ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM_PED 0x1 | |
27 | #define ADV748X_AFE_STD_AD_PAL_N_NTSC_J_SECAM 0x2 | |
28 | #define ADV748X_AFE_STD_AD_PAL_N_NTSC_M_SECAM 0x3 | |
29 | #define ADV748X_AFE_STD_NTSC_J 0x4 | |
30 | #define ADV748X_AFE_STD_NTSC_M 0x5 | |
31 | #define ADV748X_AFE_STD_PAL60 0x6 | |
32 | #define ADV748X_AFE_STD_NTSC_443 0x7 | |
33 | #define ADV748X_AFE_STD_PAL_BG 0x8 | |
34 | #define ADV748X_AFE_STD_PAL_N 0x9 | |
35 | #define ADV748X_AFE_STD_PAL_M 0xa | |
36 | #define ADV748X_AFE_STD_PAL_M_PED 0xb | |
37 | #define ADV748X_AFE_STD_PAL_COMB_N 0xc | |
38 | #define ADV748X_AFE_STD_PAL_COMB_N_PED 0xd | |
39 | #define ADV748X_AFE_STD_PAL_SECAM 0xe | |
40 | #define ADV748X_AFE_STD_PAL_SECAM_PED 0xf | |
41 | ||
42 | static int adv748x_afe_read_ro_map(struct adv748x_state *state, u8 reg) | |
43 | { | |
44 | int ret; | |
45 | ||
46 | /* Select SDP Read-Only Main Map */ | |
47 | ret = sdp_write(state, ADV748X_SDP_MAP_SEL, | |
48 | ADV748X_SDP_MAP_SEL_RO_MAIN); | |
49 | if (ret < 0) | |
50 | return ret; | |
51 | ||
52 | return sdp_read(state, reg); | |
53 | } | |
54 | ||
55 | static int adv748x_afe_status(struct adv748x_afe *afe, u32 *signal, | |
56 | v4l2_std_id *std) | |
57 | { | |
58 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
59 | int info; | |
60 | ||
61 | /* Read status from reg 0x10 of SDP RO Map */ | |
62 | info = adv748x_afe_read_ro_map(state, ADV748X_SDP_RO_10); | |
63 | if (info < 0) | |
64 | return info; | |
65 | ||
66 | if (signal) | |
67 | *signal = info & ADV748X_SDP_RO_10_IN_LOCK ? | |
68 | 0 : V4L2_IN_ST_NO_SIGNAL; | |
69 | ||
70 | if (!std) | |
71 | return 0; | |
72 | ||
73 | /* Standard not valid if there is no signal */ | |
74 | if (!(info & ADV748X_SDP_RO_10_IN_LOCK)) { | |
75 | *std = V4L2_STD_UNKNOWN; | |
76 | return 0; | |
77 | } | |
78 | ||
79 | switch (info & 0x70) { | |
80 | case 0x00: | |
81 | *std = V4L2_STD_NTSC; | |
82 | break; | |
83 | case 0x10: | |
84 | *std = V4L2_STD_NTSC_443; | |
85 | break; | |
86 | case 0x20: | |
87 | *std = V4L2_STD_PAL_M; | |
88 | break; | |
89 | case 0x30: | |
90 | *std = V4L2_STD_PAL_60; | |
91 | break; | |
92 | case 0x40: | |
93 | *std = V4L2_STD_PAL; | |
94 | break; | |
95 | case 0x50: | |
96 | *std = V4L2_STD_SECAM; | |
97 | break; | |
98 | case 0x60: | |
99 | *std = V4L2_STD_PAL_Nc | V4L2_STD_PAL_N; | |
100 | break; | |
101 | case 0x70: | |
102 | *std = V4L2_STD_SECAM; | |
103 | break; | |
104 | default: | |
105 | *std = V4L2_STD_UNKNOWN; | |
106 | break; | |
107 | } | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | static void adv748x_afe_fill_format(struct adv748x_afe *afe, | |
113 | struct v4l2_mbus_framefmt *fmt) | |
114 | { | |
115 | memset(fmt, 0, sizeof(*fmt)); | |
116 | ||
117 | fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; | |
118 | fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; | |
119 | fmt->field = V4L2_FIELD_ALTERNATE; | |
120 | ||
121 | fmt->width = 720; | |
122 | fmt->height = afe->curr_norm & V4L2_STD_525_60 ? 480 : 576; | |
123 | ||
124 | /* Field height */ | |
125 | fmt->height /= 2; | |
126 | } | |
127 | ||
128 | static int adv748x_afe_std(v4l2_std_id std) | |
129 | { | |
130 | if (std == V4L2_STD_PAL_60) | |
131 | return ADV748X_AFE_STD_PAL60; | |
132 | if (std == V4L2_STD_NTSC_443) | |
133 | return ADV748X_AFE_STD_NTSC_443; | |
134 | if (std == V4L2_STD_PAL_N) | |
135 | return ADV748X_AFE_STD_PAL_N; | |
136 | if (std == V4L2_STD_PAL_M) | |
137 | return ADV748X_AFE_STD_PAL_M; | |
138 | if (std == V4L2_STD_PAL_Nc) | |
139 | return ADV748X_AFE_STD_PAL_COMB_N; | |
140 | if (std & V4L2_STD_NTSC) | |
141 | return ADV748X_AFE_STD_NTSC_M; | |
142 | if (std & V4L2_STD_PAL) | |
143 | return ADV748X_AFE_STD_PAL_BG; | |
144 | if (std & V4L2_STD_SECAM) | |
145 | return ADV748X_AFE_STD_PAL_SECAM; | |
146 | ||
147 | return -EINVAL; | |
148 | } | |
149 | ||
150 | static void adv748x_afe_set_video_standard(struct adv748x_state *state, | |
151 | int sdpstd) | |
152 | { | |
153 | sdp_clrset(state, ADV748X_SDP_VID_SEL, ADV748X_SDP_VID_SEL_MASK, | |
154 | (sdpstd & 0xf) << ADV748X_SDP_VID_SEL_SHIFT); | |
155 | } | |
156 | ||
157 | static int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input) | |
158 | { | |
159 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
160 | ||
161 | return sdp_write(state, ADV748X_SDP_INSEL, input); | |
162 | } | |
163 | ||
164 | static int adv748x_afe_g_pixelaspect(struct v4l2_subdev *sd, | |
165 | struct v4l2_fract *aspect) | |
166 | { | |
167 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
168 | ||
169 | if (afe->curr_norm & V4L2_STD_525_60) { | |
170 | aspect->numerator = 11; | |
171 | aspect->denominator = 10; | |
172 | } else { | |
173 | aspect->numerator = 54; | |
174 | aspect->denominator = 59; | |
175 | } | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
180 | /* ----------------------------------------------------------------------------- | |
181 | * v4l2_subdev_video_ops | |
182 | */ | |
183 | ||
184 | static int adv748x_afe_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm) | |
185 | { | |
186 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
187 | ||
188 | *norm = afe->curr_norm; | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | static int adv748x_afe_s_std(struct v4l2_subdev *sd, v4l2_std_id std) | |
194 | { | |
195 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
196 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
197 | int afe_std = adv748x_afe_std(std); | |
198 | ||
199 | if (afe_std < 0) | |
200 | return afe_std; | |
201 | ||
202 | mutex_lock(&state->mutex); | |
203 | ||
204 | adv748x_afe_set_video_standard(state, afe_std); | |
205 | afe->curr_norm = std; | |
206 | ||
207 | mutex_unlock(&state->mutex); | |
208 | ||
209 | return 0; | |
210 | } | |
211 | ||
212 | static int adv748x_afe_querystd(struct v4l2_subdev *sd, v4l2_std_id *std) | |
213 | { | |
214 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
215 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
bc66c99a | 216 | int afe_std; |
3e89586a KB |
217 | int ret; |
218 | ||
219 | mutex_lock(&state->mutex); | |
220 | ||
221 | if (afe->streaming) { | |
222 | ret = -EBUSY; | |
223 | goto unlock; | |
224 | } | |
225 | ||
226 | /* Set auto detect mode */ | |
227 | adv748x_afe_set_video_standard(state, | |
228 | ADV748X_AFE_STD_AD_PAL_BG_NTSC_J_SECAM); | |
229 | ||
230 | msleep(100); | |
231 | ||
232 | /* Read detected standard */ | |
233 | ret = adv748x_afe_status(afe, NULL, std); | |
234 | ||
bc66c99a SY |
235 | afe_std = adv748x_afe_std(afe->curr_norm); |
236 | if (afe_std < 0) | |
237 | goto unlock; | |
238 | ||
3e89586a | 239 | /* Restore original state */ |
bc66c99a | 240 | adv748x_afe_set_video_standard(state, afe_std); |
3e89586a KB |
241 | |
242 | unlock: | |
243 | mutex_unlock(&state->mutex); | |
244 | ||
245 | return ret; | |
246 | } | |
247 | ||
248 | static int adv748x_afe_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) | |
249 | { | |
250 | *norm = V4L2_STD_ALL; | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | static int adv748x_afe_g_input_status(struct v4l2_subdev *sd, u32 *status) | |
256 | { | |
257 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
258 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
259 | int ret; | |
260 | ||
261 | mutex_lock(&state->mutex); | |
262 | ||
263 | ret = adv748x_afe_status(afe, status, NULL); | |
264 | ||
265 | mutex_unlock(&state->mutex); | |
b0fe7778 | 266 | |
3e89586a KB |
267 | return ret; |
268 | } | |
269 | ||
270 | static int adv748x_afe_s_stream(struct v4l2_subdev *sd, int enable) | |
271 | { | |
272 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
273 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
2b8677ec NS |
274 | u32 signal = V4L2_IN_ST_NO_SIGNAL; |
275 | int ret; | |
3e89586a KB |
276 | |
277 | mutex_lock(&state->mutex); | |
278 | ||
279 | if (enable) { | |
280 | ret = adv748x_afe_s_input(afe, afe->input); | |
281 | if (ret) | |
282 | goto unlock; | |
283 | } | |
284 | ||
a33df6ac | 285 | ret = adv748x_tx_power(afe->tx, enable); |
3e89586a KB |
286 | if (ret) |
287 | goto unlock; | |
288 | ||
289 | afe->streaming = enable; | |
290 | ||
291 | adv748x_afe_status(afe, &signal, NULL); | |
292 | if (signal != V4L2_IN_ST_NO_SIGNAL) | |
293 | adv_dbg(state, "Detected SDP signal\n"); | |
294 | else | |
295 | adv_dbg(state, "Couldn't detect SDP video signal\n"); | |
296 | ||
297 | unlock: | |
298 | mutex_unlock(&state->mutex); | |
299 | ||
300 | return ret; | |
301 | } | |
302 | ||
303 | static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = { | |
304 | .g_std = adv748x_afe_g_std, | |
305 | .s_std = adv748x_afe_s_std, | |
306 | .querystd = adv748x_afe_querystd, | |
307 | .g_tvnorms = adv748x_afe_g_tvnorms, | |
308 | .g_input_status = adv748x_afe_g_input_status, | |
309 | .s_stream = adv748x_afe_s_stream, | |
310 | .g_pixelaspect = adv748x_afe_g_pixelaspect, | |
311 | }; | |
312 | ||
313 | /* ----------------------------------------------------------------------------- | |
314 | * v4l2_subdev_pad_ops | |
315 | */ | |
316 | ||
317 | static int adv748x_afe_propagate_pixelrate(struct adv748x_afe *afe) | |
318 | { | |
319 | struct v4l2_subdev *tx; | |
3e89586a KB |
320 | |
321 | tx = adv748x_get_remote_sd(&afe->pads[ADV748X_AFE_SOURCE]); | |
322 | if (!tx) | |
323 | return -ENOLINK; | |
324 | ||
50eea4ab LP |
325 | /* |
326 | * The ADV748x ADC sampling frequency is twice the externally supplied | |
327 | * clock whose frequency is required to be 28.63636 MHz. It oversamples | |
328 | * with a factor of 4 resulting in a pixel rate of 14.3180180 MHz. | |
329 | */ | |
330 | return adv748x_csi2_set_pixelrate(tx, 14318180); | |
3e89586a KB |
331 | } |
332 | ||
333 | static int adv748x_afe_enum_mbus_code(struct v4l2_subdev *sd, | |
334 | struct v4l2_subdev_pad_config *cfg, | |
335 | struct v4l2_subdev_mbus_code_enum *code) | |
336 | { | |
337 | if (code->index != 0) | |
338 | return -EINVAL; | |
339 | ||
340 | code->code = MEDIA_BUS_FMT_UYVY8_2X8; | |
341 | ||
342 | return 0; | |
343 | } | |
344 | ||
345 | static int adv748x_afe_get_format(struct v4l2_subdev *sd, | |
346 | struct v4l2_subdev_pad_config *cfg, | |
347 | struct v4l2_subdev_format *sdformat) | |
348 | { | |
349 | struct adv748x_afe *afe = adv748x_sd_to_afe(sd); | |
350 | struct v4l2_mbus_framefmt *mbusformat; | |
351 | ||
352 | /* It makes no sense to get the format of the analog sink pads */ | |
353 | if (sdformat->pad != ADV748X_AFE_SOURCE) | |
354 | return -EINVAL; | |
355 | ||
356 | if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) { | |
357 | mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); | |
358 | sdformat->format = *mbusformat; | |
359 | } else { | |
360 | adv748x_afe_fill_format(afe, &sdformat->format); | |
361 | adv748x_afe_propagate_pixelrate(afe); | |
362 | } | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | static int adv748x_afe_set_format(struct v4l2_subdev *sd, | |
368 | struct v4l2_subdev_pad_config *cfg, | |
369 | struct v4l2_subdev_format *sdformat) | |
370 | { | |
371 | struct v4l2_mbus_framefmt *mbusformat; | |
372 | ||
373 | /* It makes no sense to get the format of the analog sink pads */ | |
374 | if (sdformat->pad != ADV748X_AFE_SOURCE) | |
375 | return -EINVAL; | |
376 | ||
377 | if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) | |
378 | return adv748x_afe_get_format(sd, cfg, sdformat); | |
379 | ||
380 | mbusformat = v4l2_subdev_get_try_format(sd, cfg, sdformat->pad); | |
381 | *mbusformat = sdformat->format; | |
382 | ||
383 | return 0; | |
384 | } | |
385 | ||
386 | static const struct v4l2_subdev_pad_ops adv748x_afe_pad_ops = { | |
387 | .enum_mbus_code = adv748x_afe_enum_mbus_code, | |
388 | .set_fmt = adv748x_afe_set_format, | |
389 | .get_fmt = adv748x_afe_get_format, | |
390 | }; | |
391 | ||
392 | /* ----------------------------------------------------------------------------- | |
393 | * v4l2_subdev_ops | |
394 | */ | |
395 | ||
396 | static const struct v4l2_subdev_ops adv748x_afe_ops = { | |
397 | .video = &adv748x_afe_video_ops, | |
398 | .pad = &adv748x_afe_pad_ops, | |
399 | }; | |
400 | ||
401 | /* ----------------------------------------------------------------------------- | |
402 | * Controls | |
403 | */ | |
404 | ||
405 | static const char * const afe_ctrl_frp_menu[] = { | |
406 | "Disabled", | |
407 | "Solid Blue", | |
408 | "Color Bars", | |
409 | "Grey Ramp", | |
410 | "Cb Ramp", | |
411 | "Cr Ramp", | |
412 | "Boundary" | |
413 | }; | |
414 | ||
415 | static int adv748x_afe_s_ctrl(struct v4l2_ctrl *ctrl) | |
416 | { | |
417 | struct adv748x_afe *afe = adv748x_ctrl_to_afe(ctrl); | |
418 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
419 | bool enable; | |
420 | int ret; | |
421 | ||
422 | ret = sdp_write(state, 0x0e, 0x00); | |
423 | if (ret < 0) | |
424 | return ret; | |
425 | ||
426 | switch (ctrl->id) { | |
427 | case V4L2_CID_BRIGHTNESS: | |
428 | ret = sdp_write(state, ADV748X_SDP_BRI, ctrl->val); | |
429 | break; | |
430 | case V4L2_CID_HUE: | |
431 | /* Hue is inverted according to HSL chart */ | |
432 | ret = sdp_write(state, ADV748X_SDP_HUE, -ctrl->val); | |
433 | break; | |
434 | case V4L2_CID_CONTRAST: | |
435 | ret = sdp_write(state, ADV748X_SDP_CON, ctrl->val); | |
436 | break; | |
437 | case V4L2_CID_SATURATION: | |
438 | ret = sdp_write(state, ADV748X_SDP_SD_SAT_U, ctrl->val); | |
439 | if (ret) | |
440 | break; | |
441 | ret = sdp_write(state, ADV748X_SDP_SD_SAT_V, ctrl->val); | |
442 | break; | |
443 | case V4L2_CID_TEST_PATTERN: | |
444 | enable = !!ctrl->val; | |
445 | ||
446 | /* Enable/Disable Color bar test patterns */ | |
447 | ret = sdp_clrset(state, ADV748X_SDP_DEF, ADV748X_SDP_DEF_VAL_EN, | |
448 | enable); | |
449 | if (ret) | |
450 | break; | |
451 | ret = sdp_clrset(state, ADV748X_SDP_FRP, ADV748X_SDP_FRP_MASK, | |
452 | enable ? ctrl->val - 1 : 0); | |
453 | break; | |
454 | default: | |
455 | return -EINVAL; | |
456 | } | |
457 | ||
458 | return ret; | |
459 | } | |
460 | ||
461 | static const struct v4l2_ctrl_ops adv748x_afe_ctrl_ops = { | |
462 | .s_ctrl = adv748x_afe_s_ctrl, | |
463 | }; | |
464 | ||
465 | static int adv748x_afe_init_controls(struct adv748x_afe *afe) | |
466 | { | |
467 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
468 | ||
469 | v4l2_ctrl_handler_init(&afe->ctrl_hdl, 5); | |
470 | ||
471 | /* Use our mutex for the controls */ | |
472 | afe->ctrl_hdl.lock = &state->mutex; | |
473 | ||
474 | v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, | |
475 | V4L2_CID_BRIGHTNESS, ADV748X_SDP_BRI_MIN, | |
476 | ADV748X_SDP_BRI_MAX, 1, ADV748X_SDP_BRI_DEF); | |
477 | v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, | |
478 | V4L2_CID_CONTRAST, ADV748X_SDP_CON_MIN, | |
479 | ADV748X_SDP_CON_MAX, 1, ADV748X_SDP_CON_DEF); | |
480 | v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, | |
481 | V4L2_CID_SATURATION, ADV748X_SDP_SAT_MIN, | |
482 | ADV748X_SDP_SAT_MAX, 1, ADV748X_SDP_SAT_DEF); | |
483 | v4l2_ctrl_new_std(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, | |
484 | V4L2_CID_HUE, ADV748X_SDP_HUE_MIN, | |
485 | ADV748X_SDP_HUE_MAX, 1, ADV748X_SDP_HUE_DEF); | |
486 | ||
487 | v4l2_ctrl_new_std_menu_items(&afe->ctrl_hdl, &adv748x_afe_ctrl_ops, | |
488 | V4L2_CID_TEST_PATTERN, | |
489 | ARRAY_SIZE(afe_ctrl_frp_menu) - 1, | |
490 | 0, 0, afe_ctrl_frp_menu); | |
491 | ||
492 | afe->sd.ctrl_handler = &afe->ctrl_hdl; | |
493 | if (afe->ctrl_hdl.error) { | |
494 | v4l2_ctrl_handler_free(&afe->ctrl_hdl); | |
495 | return afe->ctrl_hdl.error; | |
496 | } | |
497 | ||
498 | return v4l2_ctrl_handler_setup(&afe->ctrl_hdl); | |
499 | } | |
500 | ||
501 | int adv748x_afe_init(struct adv748x_afe *afe) | |
502 | { | |
503 | struct adv748x_state *state = adv748x_afe_to_state(afe); | |
504 | int ret; | |
505 | unsigned int i; | |
506 | ||
507 | afe->input = 0; | |
508 | afe->streaming = false; | |
509 | afe->curr_norm = V4L2_STD_NTSC_M; | |
510 | ||
511 | adv748x_subdev_init(&afe->sd, state, &adv748x_afe_ops, | |
512 | MEDIA_ENT_F_ATV_DECODER, "afe"); | |
513 | ||
514 | /* Identify the first connector found as a default input if set */ | |
515 | for (i = ADV748X_PORT_AIN0; i <= ADV748X_PORT_AIN7; i++) { | |
516 | /* Inputs and ports are 1-indexed to match the data sheet */ | |
517 | if (state->endpoints[i]) { | |
518 | afe->input = i; | |
519 | break; | |
520 | } | |
521 | } | |
522 | ||
523 | adv748x_afe_s_input(afe, afe->input); | |
524 | ||
525 | adv_dbg(state, "AFE Default input set to %d\n", afe->input); | |
526 | ||
527 | /* Entity pads and sinks are 0-indexed to match the pads */ | |
528 | for (i = ADV748X_AFE_SINK_AIN0; i <= ADV748X_AFE_SINK_AIN7; i++) | |
529 | afe->pads[i].flags = MEDIA_PAD_FL_SINK; | |
530 | ||
531 | afe->pads[ADV748X_AFE_SOURCE].flags = MEDIA_PAD_FL_SOURCE; | |
532 | ||
533 | ret = media_entity_pads_init(&afe->sd.entity, ADV748X_AFE_NR_PADS, | |
534 | afe->pads); | |
535 | if (ret) | |
536 | return ret; | |
537 | ||
538 | ret = adv748x_afe_init_controls(afe); | |
539 | if (ret) | |
540 | goto error; | |
541 | ||
542 | return 0; | |
543 | ||
544 | error: | |
545 | media_entity_cleanup(&afe->sd.entity); | |
546 | ||
547 | return ret; | |
548 | } | |
549 | ||
550 | void adv748x_afe_cleanup(struct adv748x_afe *afe) | |
551 | { | |
552 | v4l2_device_unregister_subdev(&afe->sd); | |
553 | media_entity_cleanup(&afe->sd.entity); | |
554 | v4l2_ctrl_handler_free(&afe->ctrl_hdl); | |
555 | } |