2018-01-12 01:26:22 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2016-02-05 12:02:43 +03:00
/*
* Media Controller ancillary functions
*
2016-06-14 21:17:40 +03:00
* Copyright ( c ) 2016 Mauro Carvalho Chehab < mchehab @ kernel . org >
2016-02-12 02:41:25 +03:00
* Copyright ( C ) 2016 Shuah Khan < shuahkh @ osg . samsung . com >
2016-02-21 19:25:09 +03:00
* Copyright ( C ) 2006 - 2010 Nokia Corporation
* Copyright ( c ) 2016 Intel Corporation .
2016-02-05 12:02:43 +03:00
*/
# include <linux/module.h>
2016-02-21 19:25:09 +03:00
# include <linux/pci.h>
# include <linux/usb.h>
2016-02-12 02:41:25 +03:00
# include <media/media-device.h>
2016-02-21 19:25:09 +03:00
# include <media/media-entity.h>
2016-02-12 02:41:25 +03:00
# include <media/v4l2-fh.h>
2016-02-05 12:02:43 +03:00
# include <media/v4l2-mc.h>
2016-02-21 19:25:09 +03:00
# include <media/v4l2-subdev.h>
2016-02-12 02:41:25 +03:00
# include <media/videobuf2-core.h>
2016-02-05 12:02:43 +03:00
int v4l2_mc_create_media_graph ( struct media_device * mdev )
{
struct media_entity * entity ;
2016-02-12 13:07:59 +03:00
struct media_entity * if_vid = NULL , * if_aud = NULL ;
2016-04-03 15:03:49 +03:00
struct media_entity * tuner = NULL , * decoder = NULL ;
2016-02-05 12:02:43 +03:00
struct media_entity * io_v4l = NULL , * io_vbi = NULL , * io_swradio = NULL ;
bool is_webcam = false ;
u32 flags ;
2018-07-31 16:22:40 +03:00
int ret , pad_sink , pad_source ;
2016-02-05 12:02:43 +03:00
if ( ! mdev )
return 0 ;
media_device_for_each_entity ( entity , mdev ) {
switch ( entity - > function ) {
case MEDIA_ENT_F_IF_VID_DECODER :
if_vid = entity ;
break ;
case MEDIA_ENT_F_IF_AUD_DECODER :
if_aud = entity ;
break ;
case MEDIA_ENT_F_TUNER :
tuner = entity ;
break ;
case MEDIA_ENT_F_ATV_DECODER :
decoder = entity ;
break ;
case MEDIA_ENT_F_IO_V4L :
io_v4l = entity ;
break ;
case MEDIA_ENT_F_IO_VBI :
io_vbi = entity ;
break ;
case MEDIA_ENT_F_IO_SWRADIO :
io_swradio = entity ;
break ;
case MEDIA_ENT_F_CAM_SENSOR :
is_webcam = true ;
break ;
}
}
/* It should have at least one I/O entity */
2018-09-15 07:00:31 +03:00
if ( ! io_v4l & & ! io_vbi & & ! io_swradio ) {
dev_warn ( mdev - > dev , " Didn't find any I/O entity \n " ) ;
2016-02-05 12:02:43 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
/*
* Here , webcams are modelled on a very simple way : the sensor is
* connected directly to the I / O entity . All dirty details , like
* scaler and crop HW are hidden . While such mapping is not enough
* for mc - centric hardware , it is enough for v4l2 interface centric
* PC - consumer ' s hardware .
*/
if ( is_webcam ) {
2018-09-15 07:00:31 +03:00
if ( ! io_v4l ) {
dev_warn ( mdev - > dev , " Didn't find a MEDIA_ENT_F_IO_V4L \n " ) ;
2016-02-05 12:02:43 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
media_device_for_each_entity ( entity , mdev ) {
if ( entity - > function ! = MEDIA_ENT_F_CAM_SENSOR )
continue ;
ret = media_create_pad_link ( entity , 0 ,
io_v4l , 0 ,
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " Failed to create a sensor link \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
}
if ( ! decoder )
return 0 ;
}
/* The device isn't a webcam. So, it should have a decoder */
2018-09-15 07:00:31 +03:00
if ( ! decoder ) {
dev_warn ( mdev - > dev , " Decoder not found \n " ) ;
2016-02-05 12:02:43 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
/* Link the tuner and IF video output pads */
if ( tuner ) {
if ( if_vid ) {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( tuner , false ,
PAD_SIGNAL_ANALOG ) ;
pad_sink = media_get_pad_index ( if_vid , true ,
PAD_SIGNAL_ANALOG ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 | | pad_sink < 0 ) {
dev_warn ( mdev - > dev , " Couldn't get tuner and/or PLL pad(s): (%d, %d) \n " ,
pad_source , pad_sink ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( tuner , pad_source ,
if_vid , pad_sink ,
2016-02-05 12:02:43 +03:00
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " Couldn't create tuner->PLL link) \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( if_vid , false ,
PAD_SIGNAL_ANALOG ) ;
pad_sink = media_get_pad_index ( decoder , true ,
PAD_SIGNAL_ANALOG ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 | | pad_sink < 0 ) {
dev_warn ( mdev - > dev , " get decoder and/or PLL pad(s): (%d, %d) \n " ,
pad_source , pad_sink ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( if_vid , pad_source ,
decoder , pad_sink ,
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " couldn't link PLL to decoder \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
} else {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( tuner , false ,
PAD_SIGNAL_ANALOG ) ;
pad_sink = media_get_pad_index ( decoder , true ,
PAD_SIGNAL_ANALOG ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 | | pad_sink < 0 ) {
dev_warn ( mdev - > dev , " couldn't get tuner and/or decoder pad(s): (%d, %d) \n " ,
pad_source , pad_sink ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( tuner , pad_source ,
decoder , pad_sink ,
MEDIA_LNK_FL_ENABLED ) ;
2016-02-05 12:02:43 +03:00
if ( ret )
return ret ;
}
if ( if_aud ) {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( tuner , false ,
PAD_SIGNAL_AUDIO ) ;
pad_sink = media_get_pad_index ( if_aud , true ,
PAD_SIGNAL_AUDIO ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 | | pad_sink < 0 ) {
dev_warn ( mdev - > dev , " couldn't get tuner and/or decoder pad(s) for audio: (%d, %d) \n " ,
pad_source , pad_sink ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( tuner , pad_source ,
if_aud , pad_sink ,
2016-02-05 12:02:43 +03:00
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " couldn't link tuner->audio PLL \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
} else {
if_aud = tuner ;
}
}
/* Create demod to V4L, VBI and SDR radio links */
if ( io_v4l ) {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( decoder , false , PAD_SIGNAL_DV ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 ) {
dev_warn ( mdev - > dev , " couldn't get decoder output pad for V4L I/O \n " ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( decoder , pad_source ,
io_v4l , 0 ,
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " couldn't link decoder output to V4L I/O \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
}
if ( io_swradio ) {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( decoder , false , PAD_SIGNAL_DV ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 ) {
dev_warn ( mdev - > dev , " couldn't get decoder output pad for SDR \n " ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( decoder , pad_source ,
io_swradio , 0 ,
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " couldn't link decoder output to SDR \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
}
if ( io_vbi ) {
2018-07-31 16:22:40 +03:00
pad_source = media_get_pad_index ( decoder , false , PAD_SIGNAL_DV ) ;
2018-09-15 07:00:31 +03:00
if ( pad_source < 0 ) {
dev_warn ( mdev - > dev , " couldn't get decoder output pad for VBI \n " ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2018-07-31 16:22:40 +03:00
ret = media_create_pad_link ( decoder , pad_source ,
2016-02-05 12:02:43 +03:00
io_vbi , 0 ,
MEDIA_LNK_FL_ENABLED ) ;
2018-09-15 07:00:31 +03:00
if ( ret ) {
dev_warn ( mdev - > dev , " couldn't link decoder output to VBI \n " ) ;
2016-02-05 12:02:43 +03:00
return ret ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
}
/* Create links for the media connectors */
flags = MEDIA_LNK_FL_ENABLED ;
media_device_for_each_entity ( entity , mdev ) {
switch ( entity - > function ) {
case MEDIA_ENT_F_CONN_RF :
if ( ! tuner )
continue ;
2018-07-31 16:22:40 +03:00
pad_sink = media_get_pad_index ( tuner , true ,
PAD_SIGNAL_ANALOG ) ;
2018-09-15 07:00:31 +03:00
if ( pad_sink < 0 ) {
dev_warn ( mdev - > dev , " couldn't get tuner analog pad sink \n " ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
ret = media_create_pad_link ( entity , 0 , tuner ,
2018-07-31 16:22:40 +03:00
pad_sink ,
2016-02-05 12:02:43 +03:00
flags ) ;
break ;
case MEDIA_ENT_F_CONN_SVIDEO :
case MEDIA_ENT_F_CONN_COMPOSITE :
2018-07-31 16:22:40 +03:00
pad_sink = media_get_pad_index ( decoder , true ,
PAD_SIGNAL_ANALOG ) ;
2018-09-15 07:00:31 +03:00
if ( pad_sink < 0 ) {
dev_warn ( mdev - > dev , " couldn't get tuner analog pad sink \n " ) ;
2018-07-31 16:22:40 +03:00
return - EINVAL ;
2018-09-15 07:00:31 +03:00
}
2016-02-05 12:02:43 +03:00
ret = media_create_pad_link ( entity , 0 , decoder ,
2018-07-31 16:22:40 +03:00
pad_sink ,
2016-02-05 12:02:43 +03:00
flags ) ;
break ;
default :
continue ;
}
if ( ret )
return ret ;
flags = 0 ;
}
2016-03-02 16:11:41 +03:00
2016-02-05 12:02:43 +03:00
return 0 ;
}
EXPORT_SYMBOL_GPL ( v4l2_mc_create_media_graph ) ;
2016-02-12 02:41:25 +03:00
int v4l_enable_media_source ( struct video_device * vdev )
{
struct media_device * mdev = vdev - > entity . graph_obj . mdev ;
2016-11-30 02:59:54 +03:00
int ret = 0 , err ;
2016-02-12 02:41:25 +03:00
2016-11-30 02:59:54 +03:00
if ( ! mdev )
2016-02-12 02:41:25 +03:00
return 0 ;
2016-11-30 02:59:54 +03:00
mutex_lock ( & mdev - > graph_mutex ) ;
if ( ! mdev - > enable_source )
goto end ;
err = mdev - > enable_source ( & vdev - > entity , & vdev - > pipe ) ;
if ( err )
ret = - EBUSY ;
end :
mutex_unlock ( & mdev - > graph_mutex ) ;
return ret ;
2016-02-12 02:41:25 +03:00
}
EXPORT_SYMBOL_GPL ( v4l_enable_media_source ) ;
void v4l_disable_media_source ( struct video_device * vdev )
{
struct media_device * mdev = vdev - > entity . graph_obj . mdev ;
2016-11-30 02:59:54 +03:00
if ( mdev ) {
mutex_lock ( & mdev - > graph_mutex ) ;
if ( mdev - > disable_source )
mdev - > disable_source ( & vdev - > entity ) ;
mutex_unlock ( & mdev - > graph_mutex ) ;
}
2016-02-12 02:41:25 +03:00
}
EXPORT_SYMBOL_GPL ( v4l_disable_media_source ) ;
int v4l_vb2q_enable_media_source ( struct vb2_queue * q )
{
struct v4l2_fh * fh = q - > owner ;
2016-03-04 05:24:58 +03:00
if ( fh & & fh - > vdev )
return v4l_enable_media_source ( fh - > vdev ) ;
return 0 ;
2016-02-12 02:41:25 +03:00
}
EXPORT_SYMBOL_GPL ( v4l_vb2q_enable_media_source ) ;
2016-02-21 19:25:09 +03:00
/* -----------------------------------------------------------------------------
* Pipeline power management
*
* Entities must be powered up when part of a pipeline that contains at least
* one open video device node .
*
* To achieve this use the entity use_count field to track the number of users .
* For entities corresponding to video device nodes the use_count field stores
* the users count of the node . For entities corresponding to subdevs the
* use_count field stores the total number of users of all video device nodes
* in the pipeline .
*
* The v4l2_pipeline_pm_use ( ) function must be called in the open ( ) and
* close ( ) handlers of video device nodes . It increments or decrements the use
* count of all subdev entities in the pipeline .
*
* To react to link management on powered pipelines , the link setup notification
* callback updates the use count of all entities in the source and sink sides
* of the link .
*/
/*
* pipeline_pm_use_count - Count the number of users of a pipeline
* @ entity : The entity
*
* Return the total number of users of all video device nodes in the pipeline .
*/
static int pipeline_pm_use_count ( struct media_entity * entity ,
2016-11-21 19:48:30 +03:00
struct media_graph * graph )
2016-02-21 19:25:09 +03:00
{
int use = 0 ;
2016-11-21 19:48:30 +03:00
media_graph_walk_start ( graph , entity ) ;
2016-02-21 19:25:09 +03:00
2016-11-21 19:48:30 +03:00
while ( ( entity = media_graph_walk_next ( graph ) ) ) {
2016-02-29 14:45:45 +03:00
if ( is_media_entity_v4l2_video_device ( entity ) )
2016-02-21 19:25:09 +03:00
use + = entity - > use_count ;
}
return use ;
}
/*
* pipeline_pm_power_one - Apply power change to an entity
* @ entity : The entity
* @ change : Use count change
*
* Change the entity use count by @ change . If the entity is a subdev update its
* power state by calling the core : : s_power operation when the use count goes
* from 0 to ! = 0 or from ! = 0 to 0.
*
* Return 0 on success or a negative error code on failure .
*/
static int pipeline_pm_power_one ( struct media_entity * entity , int change )
{
struct v4l2_subdev * subdev ;
int ret ;
subdev = is_media_entity_v4l2_subdev ( entity )
? media_entity_to_v4l2_subdev ( entity ) : NULL ;
if ( entity - > use_count = = 0 & & change > 0 & & subdev ! = NULL ) {
ret = v4l2_subdev_call ( subdev , core , s_power , 1 ) ;
if ( ret < 0 & & ret ! = - ENOIOCTLCMD )
return ret ;
}
entity - > use_count + = change ;
WARN_ON ( entity - > use_count < 0 ) ;
if ( entity - > use_count = = 0 & & change < 0 & & subdev ! = NULL )
v4l2_subdev_call ( subdev , core , s_power , 0 ) ;
return 0 ;
}
/*
* pipeline_pm_power - Apply power change to all entities in a pipeline
* @ entity : The entity
* @ change : Use count change
*
* Walk the pipeline to update the use count and the power state of all non - node
* entities .
*
* Return 0 on success or a negative error code on failure .
*/
static int pipeline_pm_power ( struct media_entity * entity , int change ,
2016-11-21 19:48:30 +03:00
struct media_graph * graph )
2016-02-21 19:25:09 +03:00
{
struct media_entity * first = entity ;
int ret = 0 ;
if ( ! change )
return 0 ;
2016-11-21 19:48:30 +03:00
media_graph_walk_start ( graph , entity ) ;
2016-02-21 19:25:09 +03:00
2016-11-21 19:48:30 +03:00
while ( ! ret & & ( entity = media_graph_walk_next ( graph ) ) )
2016-02-21 19:25:09 +03:00
if ( is_media_entity_v4l2_subdev ( entity ) )
ret = pipeline_pm_power_one ( entity , change ) ;
if ( ! ret )
return ret ;
2016-11-21 19:48:30 +03:00
media_graph_walk_start ( graph , first ) ;
2016-02-21 19:25:09 +03:00
2016-11-21 19:48:30 +03:00
while ( ( first = media_graph_walk_next ( graph ) )
2016-02-21 19:25:09 +03:00
& & first ! = entity )
if ( is_media_entity_v4l2_subdev ( first ) )
pipeline_pm_power_one ( first , - change ) ;
return ret ;
}
int v4l2_pipeline_pm_use ( struct media_entity * entity , int use )
{
struct media_device * mdev = entity - > graph_obj . mdev ;
int change = use ? 1 : - 1 ;
int ret ;
mutex_lock ( & mdev - > graph_mutex ) ;
/* Apply use count to node. */
entity - > use_count + = change ;
WARN_ON ( entity - > use_count < 0 ) ;
/* Apply power change to connected non-nodes. */
ret = pipeline_pm_power ( entity , change , & mdev - > pm_count_walk ) ;
if ( ret < 0 )
entity - > use_count - = change ;
mutex_unlock ( & mdev - > graph_mutex ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( v4l2_pipeline_pm_use ) ;
int v4l2_pipeline_link_notify ( struct media_link * link , u32 flags ,
unsigned int notification )
{
2016-11-21 19:48:30 +03:00
struct media_graph * graph = & link - > graph_obj . mdev - > pm_count_walk ;
2016-02-21 19:25:09 +03:00
struct media_entity * source = link - > source - > entity ;
struct media_entity * sink = link - > sink - > entity ;
int source_use ;
int sink_use ;
int ret = 0 ;
source_use = pipeline_pm_use_count ( source , graph ) ;
sink_use = pipeline_pm_use_count ( sink , graph ) ;
if ( notification = = MEDIA_DEV_NOTIFY_POST_LINK_CH & &
! ( flags & MEDIA_LNK_FL_ENABLED ) ) {
/* Powering off entities is assumed to never fail. */
pipeline_pm_power ( source , - sink_use , graph ) ;
pipeline_pm_power ( sink , - source_use , graph ) ;
return 0 ;
}
if ( notification = = MEDIA_DEV_NOTIFY_PRE_LINK_CH & &
( flags & MEDIA_LNK_FL_ENABLED ) ) {
ret = pipeline_pm_power ( source , sink_use , graph ) ;
if ( ret < 0 )
return ret ;
ret = pipeline_pm_power ( sink , source_use , graph ) ;
if ( ret < 0 )
pipeline_pm_power ( source , - sink_use , graph ) ;
}
return ret ;
}
EXPORT_SYMBOL_GPL ( v4l2_pipeline_link_notify ) ;