1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | |
3 | /* |
4 | * Media Controller ancillary functions |
5 | * |
6 | * Copyright (c) 2016 Mauro Carvalho Chehab <mchehab@kernel.org> |
7 | * Copyright (C) 2016 Shuah Khan <shuahkh@osg.samsung.com> |
8 | * Copyright (C) 2006-2010 Nokia Corporation |
9 | * Copyright (c) 2016 Intel Corporation. |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/pci.h> |
14 | #include <linux/usb.h> |
15 | #include <media/media-device.h> |
16 | #include <media/media-entity.h> |
17 | #include <media/v4l2-fh.h> |
18 | #include <media/v4l2-mc.h> |
19 | #include <media/v4l2-subdev.h> |
20 | #include <media/videobuf2-core.h> |
21 | |
22 | int v4l2_mc_create_media_graph(struct media_device *mdev) |
23 | |
24 | { |
25 | struct media_entity *entity; |
26 | struct media_entity *if_vid = NULL, *if_aud = NULL; |
27 | struct media_entity *tuner = NULL, *decoder = NULL; |
28 | struct media_entity *io_v4l = NULL, *io_vbi = NULL, *io_swradio = NULL; |
29 | bool is_webcam = false; |
30 | u32 flags; |
31 | int ret, pad_sink, pad_source; |
32 | |
33 | if (!mdev) |
34 | return 0; |
35 | |
36 | media_device_for_each_entity(entity, mdev) { |
37 | switch (entity->function) { |
38 | case MEDIA_ENT_F_IF_VID_DECODER: |
39 | if_vid = entity; |
40 | break; |
41 | case MEDIA_ENT_F_IF_AUD_DECODER: |
42 | if_aud = entity; |
43 | break; |
44 | case MEDIA_ENT_F_TUNER: |
45 | tuner = entity; |
46 | break; |
47 | case MEDIA_ENT_F_ATV_DECODER: |
48 | decoder = entity; |
49 | break; |
50 | case MEDIA_ENT_F_IO_V4L: |
51 | io_v4l = entity; |
52 | break; |
53 | case MEDIA_ENT_F_IO_VBI: |
54 | io_vbi = entity; |
55 | break; |
56 | case MEDIA_ENT_F_IO_SWRADIO: |
57 | io_swradio = entity; |
58 | break; |
59 | case MEDIA_ENT_F_CAM_SENSOR: |
60 | is_webcam = true; |
61 | break; |
62 | } |
63 | } |
64 | |
65 | /* It should have at least one I/O entity */ |
66 | if (!io_v4l && !io_vbi && !io_swradio) { |
67 | dev_warn(mdev->dev, "Didn't find any I/O entity\n" ); |
68 | return -EINVAL; |
69 | } |
70 | |
71 | /* |
72 | * Here, webcams are modelled on a very simple way: the sensor is |
73 | * connected directly to the I/O entity. All dirty details, like |
74 | * scaler and crop HW are hidden. While such mapping is not enough |
75 | * for mc-centric hardware, it is enough for v4l2 interface centric |
76 | * PC-consumer's hardware. |
77 | */ |
78 | if (is_webcam) { |
79 | if (!io_v4l) { |
80 | dev_warn(mdev->dev, "Didn't find a MEDIA_ENT_F_IO_V4L\n" ); |
81 | return -EINVAL; |
82 | } |
83 | |
84 | media_device_for_each_entity(entity, mdev) { |
85 | if (entity->function != MEDIA_ENT_F_CAM_SENSOR) |
86 | continue; |
87 | ret = media_create_pad_link(source: entity, source_pad: 0, |
88 | sink: io_v4l, sink_pad: 0, |
89 | MEDIA_LNK_FL_ENABLED); |
90 | if (ret) { |
91 | dev_warn(mdev->dev, "Failed to create a sensor link\n" ); |
92 | return ret; |
93 | } |
94 | } |
95 | if (!decoder) |
96 | return 0; |
97 | } |
98 | |
99 | /* The device isn't a webcam. So, it should have a decoder */ |
100 | if (!decoder) { |
101 | dev_warn(mdev->dev, "Decoder not found\n" ); |
102 | return -EINVAL; |
103 | } |
104 | |
105 | /* Link the tuner and IF video output pads */ |
106 | if (tuner) { |
107 | if (if_vid) { |
108 | pad_source = media_get_pad_index(entity: tuner, |
109 | MEDIA_PAD_FL_SOURCE, |
110 | sig_type: PAD_SIGNAL_ANALOG); |
111 | pad_sink = media_get_pad_index(entity: if_vid, |
112 | MEDIA_PAD_FL_SINK, |
113 | sig_type: PAD_SIGNAL_ANALOG); |
114 | if (pad_source < 0 || pad_sink < 0) { |
115 | dev_warn(mdev->dev, "Couldn't get tuner and/or PLL pad(s): (%d, %d)\n" , |
116 | pad_source, pad_sink); |
117 | return -EINVAL; |
118 | } |
119 | ret = media_create_pad_link(source: tuner, source_pad: pad_source, |
120 | sink: if_vid, sink_pad: pad_sink, |
121 | MEDIA_LNK_FL_ENABLED); |
122 | if (ret) { |
123 | dev_warn(mdev->dev, "Couldn't create tuner->PLL link)\n" ); |
124 | return ret; |
125 | } |
126 | |
127 | pad_source = media_get_pad_index(entity: if_vid, |
128 | MEDIA_PAD_FL_SOURCE, |
129 | sig_type: PAD_SIGNAL_ANALOG); |
130 | pad_sink = media_get_pad_index(entity: decoder, |
131 | MEDIA_PAD_FL_SINK, |
132 | sig_type: PAD_SIGNAL_ANALOG); |
133 | if (pad_source < 0 || pad_sink < 0) { |
134 | dev_warn(mdev->dev, "get decoder and/or PLL pad(s): (%d, %d)\n" , |
135 | pad_source, pad_sink); |
136 | return -EINVAL; |
137 | } |
138 | ret = media_create_pad_link(source: if_vid, source_pad: pad_source, |
139 | sink: decoder, sink_pad: pad_sink, |
140 | MEDIA_LNK_FL_ENABLED); |
141 | if (ret) { |
142 | dev_warn(mdev->dev, "couldn't link PLL to decoder\n" ); |
143 | return ret; |
144 | } |
145 | } else { |
146 | pad_source = media_get_pad_index(entity: tuner, |
147 | MEDIA_PAD_FL_SOURCE, |
148 | sig_type: PAD_SIGNAL_ANALOG); |
149 | pad_sink = media_get_pad_index(entity: decoder, |
150 | MEDIA_PAD_FL_SINK, |
151 | sig_type: PAD_SIGNAL_ANALOG); |
152 | if (pad_source < 0 || pad_sink < 0) { |
153 | dev_warn(mdev->dev, "couldn't get tuner and/or decoder pad(s): (%d, %d)\n" , |
154 | pad_source, pad_sink); |
155 | return -EINVAL; |
156 | } |
157 | ret = media_create_pad_link(source: tuner, source_pad: pad_source, |
158 | sink: decoder, sink_pad: pad_sink, |
159 | MEDIA_LNK_FL_ENABLED); |
160 | if (ret) |
161 | return ret; |
162 | } |
163 | |
164 | if (if_aud) { |
165 | pad_source = media_get_pad_index(entity: tuner, |
166 | MEDIA_PAD_FL_SOURCE, |
167 | sig_type: PAD_SIGNAL_AUDIO); |
168 | pad_sink = media_get_pad_index(entity: if_aud, |
169 | MEDIA_PAD_FL_SINK, |
170 | sig_type: PAD_SIGNAL_AUDIO); |
171 | if (pad_source < 0 || pad_sink < 0) { |
172 | dev_warn(mdev->dev, "couldn't get tuner and/or decoder pad(s) for audio: (%d, %d)\n" , |
173 | pad_source, pad_sink); |
174 | return -EINVAL; |
175 | } |
176 | ret = media_create_pad_link(source: tuner, source_pad: pad_source, |
177 | sink: if_aud, sink_pad: pad_sink, |
178 | MEDIA_LNK_FL_ENABLED); |
179 | if (ret) { |
180 | dev_warn(mdev->dev, "couldn't link tuner->audio PLL\n" ); |
181 | return ret; |
182 | } |
183 | } else { |
184 | if_aud = tuner; |
185 | } |
186 | |
187 | } |
188 | |
189 | /* Create demod to V4L, VBI and SDR radio links */ |
190 | if (io_v4l) { |
191 | pad_source = media_get_pad_index(entity: decoder, MEDIA_PAD_FL_SOURCE, |
192 | sig_type: PAD_SIGNAL_DV); |
193 | if (pad_source < 0) { |
194 | dev_warn(mdev->dev, "couldn't get decoder output pad for V4L I/O\n" ); |
195 | return -EINVAL; |
196 | } |
197 | ret = media_create_pad_link(source: decoder, source_pad: pad_source, |
198 | sink: io_v4l, sink_pad: 0, |
199 | MEDIA_LNK_FL_ENABLED); |
200 | if (ret) { |
201 | dev_warn(mdev->dev, "couldn't link decoder output to V4L I/O\n" ); |
202 | return ret; |
203 | } |
204 | } |
205 | |
206 | if (io_swradio) { |
207 | pad_source = media_get_pad_index(entity: decoder, MEDIA_PAD_FL_SOURCE, |
208 | sig_type: PAD_SIGNAL_DV); |
209 | if (pad_source < 0) { |
210 | dev_warn(mdev->dev, "couldn't get decoder output pad for SDR\n" ); |
211 | return -EINVAL; |
212 | } |
213 | ret = media_create_pad_link(source: decoder, source_pad: pad_source, |
214 | sink: io_swradio, sink_pad: 0, |
215 | MEDIA_LNK_FL_ENABLED); |
216 | if (ret) { |
217 | dev_warn(mdev->dev, "couldn't link decoder output to SDR\n" ); |
218 | return ret; |
219 | } |
220 | } |
221 | |
222 | if (io_vbi) { |
223 | pad_source = media_get_pad_index(entity: decoder, MEDIA_PAD_FL_SOURCE, |
224 | sig_type: PAD_SIGNAL_DV); |
225 | if (pad_source < 0) { |
226 | dev_warn(mdev->dev, "couldn't get decoder output pad for VBI\n" ); |
227 | return -EINVAL; |
228 | } |
229 | ret = media_create_pad_link(source: decoder, source_pad: pad_source, |
230 | sink: io_vbi, sink_pad: 0, |
231 | MEDIA_LNK_FL_ENABLED); |
232 | if (ret) { |
233 | dev_warn(mdev->dev, "couldn't link decoder output to VBI\n" ); |
234 | return ret; |
235 | } |
236 | } |
237 | |
238 | /* Create links for the media connectors */ |
239 | flags = MEDIA_LNK_FL_ENABLED; |
240 | media_device_for_each_entity(entity, mdev) { |
241 | switch (entity->function) { |
242 | case MEDIA_ENT_F_CONN_RF: |
243 | if (!tuner) |
244 | continue; |
245 | pad_sink = media_get_pad_index(entity: tuner, MEDIA_PAD_FL_SINK, |
246 | sig_type: PAD_SIGNAL_ANALOG); |
247 | if (pad_sink < 0) { |
248 | dev_warn(mdev->dev, "couldn't get tuner analog pad sink\n" ); |
249 | return -EINVAL; |
250 | } |
251 | ret = media_create_pad_link(source: entity, source_pad: 0, sink: tuner, |
252 | sink_pad: pad_sink, |
253 | flags); |
254 | break; |
255 | case MEDIA_ENT_F_CONN_SVIDEO: |
256 | case MEDIA_ENT_F_CONN_COMPOSITE: |
257 | pad_sink = media_get_pad_index(entity: decoder, |
258 | MEDIA_PAD_FL_SINK, |
259 | sig_type: PAD_SIGNAL_ANALOG); |
260 | if (pad_sink < 0) { |
261 | dev_warn(mdev->dev, "couldn't get decoder analog pad sink\n" ); |
262 | return -EINVAL; |
263 | } |
264 | ret = media_create_pad_link(source: entity, source_pad: 0, sink: decoder, |
265 | sink_pad: pad_sink, |
266 | flags); |
267 | break; |
268 | default: |
269 | continue; |
270 | } |
271 | if (ret) |
272 | return ret; |
273 | |
274 | flags = 0; |
275 | } |
276 | |
277 | return 0; |
278 | } |
279 | EXPORT_SYMBOL_GPL(v4l2_mc_create_media_graph); |
280 | |
281 | int v4l_enable_media_source(struct video_device *vdev) |
282 | { |
283 | struct media_device *mdev = vdev->entity.graph_obj.mdev; |
284 | int ret = 0, err; |
285 | |
286 | if (!mdev) |
287 | return 0; |
288 | |
289 | mutex_lock(&mdev->graph_mutex); |
290 | if (!mdev->enable_source) |
291 | goto end; |
292 | err = mdev->enable_source(&vdev->entity, &vdev->pipe); |
293 | if (err) |
294 | ret = -EBUSY; |
295 | end: |
296 | mutex_unlock(lock: &mdev->graph_mutex); |
297 | return ret; |
298 | } |
299 | EXPORT_SYMBOL_GPL(v4l_enable_media_source); |
300 | |
301 | void v4l_disable_media_source(struct video_device *vdev) |
302 | { |
303 | struct media_device *mdev = vdev->entity.graph_obj.mdev; |
304 | |
305 | if (mdev) { |
306 | mutex_lock(&mdev->graph_mutex); |
307 | if (mdev->disable_source) |
308 | mdev->disable_source(&vdev->entity); |
309 | mutex_unlock(lock: &mdev->graph_mutex); |
310 | } |
311 | } |
312 | EXPORT_SYMBOL_GPL(v4l_disable_media_source); |
313 | |
314 | int v4l_vb2q_enable_media_source(struct vb2_queue *q) |
315 | { |
316 | struct v4l2_fh *fh = q->owner; |
317 | |
318 | if (fh && fh->vdev) |
319 | return v4l_enable_media_source(fh->vdev); |
320 | return 0; |
321 | } |
322 | EXPORT_SYMBOL_GPL(v4l_vb2q_enable_media_source); |
323 | |
324 | int v4l2_create_fwnode_links_to_pad(struct v4l2_subdev *src_sd, |
325 | struct media_pad *sink, u32 flags) |
326 | { |
327 | struct fwnode_handle *endpoint; |
328 | |
329 | if (!(sink->flags & MEDIA_PAD_FL_SINK)) |
330 | return -EINVAL; |
331 | |
332 | fwnode_graph_for_each_endpoint(dev_fwnode(src_sd->dev), endpoint) { |
333 | struct fwnode_handle *remote_ep; |
334 | int src_idx, sink_idx, ret; |
335 | struct media_pad *src; |
336 | |
337 | src_idx = media_entity_get_fwnode_pad(entity: &src_sd->entity, |
338 | fwnode: endpoint, |
339 | MEDIA_PAD_FL_SOURCE); |
340 | if (src_idx < 0) { |
341 | dev_dbg(src_sd->dev, "no source pad found for %pfw\n" , |
342 | endpoint); |
343 | continue; |
344 | } |
345 | |
346 | remote_ep = fwnode_graph_get_remote_endpoint(fwnode: endpoint); |
347 | if (!remote_ep) { |
348 | dev_dbg(src_sd->dev, "no remote ep found for %pfw\n" , |
349 | endpoint); |
350 | continue; |
351 | } |
352 | |
353 | /* |
354 | * ask the sink to verify it owns the remote endpoint, |
355 | * and translate to a sink pad. |
356 | */ |
357 | sink_idx = media_entity_get_fwnode_pad(entity: sink->entity, |
358 | fwnode: remote_ep, |
359 | MEDIA_PAD_FL_SINK); |
360 | fwnode_handle_put(fwnode: remote_ep); |
361 | |
362 | if (sink_idx < 0 || sink_idx != sink->index) { |
363 | dev_dbg(src_sd->dev, |
364 | "sink pad index mismatch or error (is %d, expected %u)\n" , |
365 | sink_idx, sink->index); |
366 | continue; |
367 | } |
368 | |
369 | /* |
370 | * the source endpoint corresponds to one of its source pads, |
371 | * the source endpoint connects to an endpoint at the sink |
372 | * entity, and the sink endpoint corresponds to the sink |
373 | * pad requested, so we have found an endpoint connection |
374 | * that works, create the media link for it. |
375 | */ |
376 | |
377 | src = &src_sd->entity.pads[src_idx]; |
378 | |
379 | /* skip if link already exists */ |
380 | if (media_entity_find_link(source: src, sink)) { |
381 | dev_dbg(src_sd->dev, |
382 | "link %s:%d -> %s:%d already exists\n" , |
383 | src_sd->entity.name, src_idx, |
384 | sink->entity->name, sink_idx); |
385 | continue; |
386 | } |
387 | |
388 | dev_dbg(src_sd->dev, "creating link %s:%d -> %s:%d\n" , |
389 | src_sd->entity.name, src_idx, |
390 | sink->entity->name, sink_idx); |
391 | |
392 | ret = media_create_pad_link(source: &src_sd->entity, source_pad: src_idx, |
393 | sink: sink->entity, sink_pad: sink_idx, flags); |
394 | if (ret) { |
395 | dev_err(src_sd->dev, |
396 | "link %s:%d -> %s:%d failed with %d\n" , |
397 | src_sd->entity.name, src_idx, |
398 | sink->entity->name, sink_idx, ret); |
399 | |
400 | fwnode_handle_put(fwnode: endpoint); |
401 | return ret; |
402 | } |
403 | } |
404 | |
405 | return 0; |
406 | } |
407 | EXPORT_SYMBOL_GPL(v4l2_create_fwnode_links_to_pad); |
408 | |
409 | int v4l2_create_fwnode_links(struct v4l2_subdev *src_sd, |
410 | struct v4l2_subdev *sink_sd) |
411 | { |
412 | unsigned int i; |
413 | |
414 | for (i = 0; i < sink_sd->entity.num_pads; i++) { |
415 | struct media_pad *pad = &sink_sd->entity.pads[i]; |
416 | int ret; |
417 | |
418 | if (!(pad->flags & MEDIA_PAD_FL_SINK)) |
419 | continue; |
420 | |
421 | ret = v4l2_create_fwnode_links_to_pad(src_sd, pad, 0); |
422 | if (ret) |
423 | return ret; |
424 | } |
425 | |
426 | return 0; |
427 | } |
428 | EXPORT_SYMBOL_GPL(v4l2_create_fwnode_links); |
429 | |
430 | /* ----------------------------------------------------------------------------- |
431 | * Pipeline power management |
432 | * |
433 | * Entities must be powered up when part of a pipeline that contains at least |
434 | * one open video device node. |
435 | * |
436 | * To achieve this use the entity use_count field to track the number of users. |
437 | * For entities corresponding to video device nodes the use_count field stores |
438 | * the users count of the node. For entities corresponding to subdevs the |
439 | * use_count field stores the total number of users of all video device nodes |
440 | * in the pipeline. |
441 | * |
442 | * The v4l2_pipeline_pm_{get, put}() functions must be called in the open() and |
443 | * close() handlers of video device nodes. It increments or decrements the use |
444 | * count of all subdev entities in the pipeline. |
445 | * |
446 | * To react to link management on powered pipelines, the link setup notification |
447 | * callback updates the use count of all entities in the source and sink sides |
448 | * of the link. |
449 | */ |
450 | |
451 | /* |
452 | * pipeline_pm_use_count - Count the number of users of a pipeline |
453 | * @entity: The entity |
454 | * |
455 | * Return the total number of users of all video device nodes in the pipeline. |
456 | */ |
457 | static int pipeline_pm_use_count(struct media_entity *entity, |
458 | struct media_graph *graph) |
459 | { |
460 | int use = 0; |
461 | |
462 | media_graph_walk_start(graph, entity); |
463 | |
464 | while ((entity = media_graph_walk_next(graph))) { |
465 | if (is_media_entity_v4l2_video_device(entity)) |
466 | use += entity->use_count; |
467 | } |
468 | |
469 | return use; |
470 | } |
471 | |
472 | /* |
473 | * pipeline_pm_power_one - Apply power change to an entity |
474 | * @entity: The entity |
475 | * @change: Use count change |
476 | * |
477 | * Change the entity use count by @change. If the entity is a subdev update its |
478 | * power state by calling the core::s_power operation when the use count goes |
479 | * from 0 to != 0 or from != 0 to 0. |
480 | * |
481 | * Return 0 on success or a negative error code on failure. |
482 | */ |
483 | static int pipeline_pm_power_one(struct media_entity *entity, int change) |
484 | { |
485 | struct v4l2_subdev *subdev; |
486 | int ret; |
487 | |
488 | subdev = is_media_entity_v4l2_subdev(entity) |
489 | ? media_entity_to_v4l2_subdev(entity) : NULL; |
490 | |
491 | if (entity->use_count == 0 && change > 0 && subdev != NULL) { |
492 | ret = v4l2_subdev_call(subdev, core, s_power, 1); |
493 | if (ret < 0 && ret != -ENOIOCTLCMD) |
494 | return ret; |
495 | } |
496 | |
497 | entity->use_count += change; |
498 | WARN_ON(entity->use_count < 0); |
499 | |
500 | if (entity->use_count == 0 && change < 0 && subdev != NULL) |
501 | v4l2_subdev_call(subdev, core, s_power, 0); |
502 | |
503 | return 0; |
504 | } |
505 | |
506 | /* |
507 | * pipeline_pm_power - Apply power change to all entities in a pipeline |
508 | * @entity: The entity |
509 | * @change: Use count change |
510 | * |
511 | * Walk the pipeline to update the use count and the power state of all non-node |
512 | * entities. |
513 | * |
514 | * Return 0 on success or a negative error code on failure. |
515 | */ |
516 | static int pipeline_pm_power(struct media_entity *entity, int change, |
517 | struct media_graph *graph) |
518 | { |
519 | struct media_entity *first = entity; |
520 | int ret = 0; |
521 | |
522 | if (!change) |
523 | return 0; |
524 | |
525 | media_graph_walk_start(graph, entity); |
526 | |
527 | while (!ret && (entity = media_graph_walk_next(graph))) |
528 | if (is_media_entity_v4l2_subdev(entity)) |
529 | ret = pipeline_pm_power_one(entity, change); |
530 | |
531 | if (!ret) |
532 | return ret; |
533 | |
534 | media_graph_walk_start(graph, entity: first); |
535 | |
536 | while ((first = media_graph_walk_next(graph)) |
537 | && first != entity) |
538 | if (is_media_entity_v4l2_subdev(entity: first)) |
539 | pipeline_pm_power_one(entity: first, change: -change); |
540 | |
541 | return ret; |
542 | } |
543 | |
544 | static int v4l2_pipeline_pm_use(struct media_entity *entity, unsigned int use) |
545 | { |
546 | struct media_device *mdev = entity->graph_obj.mdev; |
547 | int change = use ? 1 : -1; |
548 | int ret; |
549 | |
550 | mutex_lock(&mdev->graph_mutex); |
551 | |
552 | /* Apply use count to node. */ |
553 | entity->use_count += change; |
554 | WARN_ON(entity->use_count < 0); |
555 | |
556 | /* Apply power change to connected non-nodes. */ |
557 | ret = pipeline_pm_power(entity, change, graph: &mdev->pm_count_walk); |
558 | if (ret < 0) |
559 | entity->use_count -= change; |
560 | |
561 | mutex_unlock(lock: &mdev->graph_mutex); |
562 | |
563 | return ret; |
564 | } |
565 | |
566 | int v4l2_pipeline_pm_get(struct media_entity *entity) |
567 | { |
568 | return v4l2_pipeline_pm_use(entity, use: 1); |
569 | } |
570 | EXPORT_SYMBOL_GPL(v4l2_pipeline_pm_get); |
571 | |
572 | void v4l2_pipeline_pm_put(struct media_entity *entity) |
573 | { |
574 | /* Powering off entities shouldn't fail. */ |
575 | WARN_ON(v4l2_pipeline_pm_use(entity, 0)); |
576 | } |
577 | EXPORT_SYMBOL_GPL(v4l2_pipeline_pm_put); |
578 | |
579 | int v4l2_pipeline_link_notify(struct media_link *link, u32 flags, |
580 | unsigned int notification) |
581 | { |
582 | struct media_graph *graph = &link->graph_obj.mdev->pm_count_walk; |
583 | struct media_entity *source = link->source->entity; |
584 | struct media_entity *sink = link->sink->entity; |
585 | int source_use; |
586 | int sink_use; |
587 | int ret = 0; |
588 | |
589 | source_use = pipeline_pm_use_count(entity: source, graph); |
590 | sink_use = pipeline_pm_use_count(entity: sink, graph); |
591 | |
592 | if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH && |
593 | !(flags & MEDIA_LNK_FL_ENABLED)) { |
594 | /* Powering off entities is assumed to never fail. */ |
595 | pipeline_pm_power(entity: source, change: -sink_use, graph); |
596 | pipeline_pm_power(entity: sink, change: -source_use, graph); |
597 | return 0; |
598 | } |
599 | |
600 | if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH && |
601 | (flags & MEDIA_LNK_FL_ENABLED)) { |
602 | |
603 | ret = pipeline_pm_power(entity: source, change: sink_use, graph); |
604 | if (ret < 0) |
605 | return ret; |
606 | |
607 | ret = pipeline_pm_power(entity: sink, change: source_use, graph); |
608 | if (ret < 0) |
609 | pipeline_pm_power(entity: source, change: -sink_use, graph); |
610 | } |
611 | |
612 | return ret; |
613 | } |
614 | EXPORT_SYMBOL_GPL(v4l2_pipeline_link_notify); |
615 | |