forked from Minki/linux
3be0bf9734
The VSP2-DL instance (present in the H3 ES2.0 and M3-N SoCs) has two LIF instances. Adapt the driver infrastructure to support multiple LIFs. Support for multiple display pipelines will be added separately. The change to the entity routing table removes the ability to connect the LIF output to the HGO or HGT histogram generators. This feature is only available on Gen2 hardware, isn't supported by the rest of the driver, and has no known use case, so this isn't an issue. Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com> Acked-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
572 lines
16 KiB
C
572 lines
16 KiB
C
/*
|
|
* vsp1_entity.c -- R-Car VSP1 Base Entity
|
|
*
|
|
* Copyright (C) 2013-2014 Renesas Electronics Corporation
|
|
*
|
|
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/gfp.h>
|
|
|
|
#include <media/media-entity.h>
|
|
#include <media/v4l2-ctrls.h>
|
|
#include <media/v4l2-subdev.h>
|
|
|
|
#include "vsp1.h"
|
|
#include "vsp1_dl.h"
|
|
#include "vsp1_entity.h"
|
|
#include "vsp1_pipe.h"
|
|
#include "vsp1_rwpf.h"
|
|
|
|
void vsp1_entity_route_setup(struct vsp1_entity *entity,
|
|
struct vsp1_pipeline *pipe,
|
|
struct vsp1_dl_list *dl)
|
|
{
|
|
struct vsp1_entity *source;
|
|
u32 route;
|
|
|
|
if (entity->type == VSP1_ENTITY_HGO) {
|
|
u32 smppt;
|
|
|
|
/*
|
|
* The HGO is a special case, its routing is configured on the
|
|
* sink pad.
|
|
*/
|
|
source = entity->sources[0];
|
|
smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT)
|
|
| (source->route->output << VI6_DPR_SMPPT_PT_SHIFT);
|
|
|
|
vsp1_dl_list_write(dl, VI6_DPR_HGO_SMPPT, smppt);
|
|
return;
|
|
} else if (entity->type == VSP1_ENTITY_HGT) {
|
|
u32 smppt;
|
|
|
|
/*
|
|
* The HGT is a special case, its routing is configured on the
|
|
* sink pad.
|
|
*/
|
|
source = entity->sources[0];
|
|
smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT)
|
|
| (source->route->output << VI6_DPR_SMPPT_PT_SHIFT);
|
|
|
|
vsp1_dl_list_write(dl, VI6_DPR_HGT_SMPPT, smppt);
|
|
return;
|
|
}
|
|
|
|
source = entity;
|
|
if (source->route->reg == 0)
|
|
return;
|
|
|
|
route = source->sink->route->inputs[source->sink_pad];
|
|
/*
|
|
* The ILV and BRS share the same data path route. The extra BRSSEL bit
|
|
* selects between the ILV and BRS.
|
|
*/
|
|
if (source->type == VSP1_ENTITY_BRS)
|
|
route |= VI6_DPR_ROUTE_BRSSEL;
|
|
vsp1_dl_list_write(dl, source->route->reg, route);
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* V4L2 Subdevice Operations
|
|
*/
|
|
|
|
/**
|
|
* vsp1_entity_get_pad_config - Get the pad configuration for an entity
|
|
* @entity: the entity
|
|
* @cfg: the TRY pad configuration
|
|
* @which: configuration selector (ACTIVE or TRY)
|
|
*
|
|
* When called with which set to V4L2_SUBDEV_FORMAT_ACTIVE the caller must hold
|
|
* the entity lock to access the returned configuration.
|
|
*
|
|
* Return the pad configuration requested by the which argument. The TRY
|
|
* configuration is passed explicitly to the function through the cfg argument
|
|
* and simply returned when requested. The ACTIVE configuration comes from the
|
|
* entity structure.
|
|
*/
|
|
struct v4l2_subdev_pad_config *
|
|
vsp1_entity_get_pad_config(struct vsp1_entity *entity,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
enum v4l2_subdev_format_whence which)
|
|
{
|
|
switch (which) {
|
|
case V4L2_SUBDEV_FORMAT_ACTIVE:
|
|
return entity->config;
|
|
case V4L2_SUBDEV_FORMAT_TRY:
|
|
default:
|
|
return cfg;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* vsp1_entity_get_pad_format - Get a pad format from storage for an entity
|
|
* @entity: the entity
|
|
* @cfg: the configuration storage
|
|
* @pad: the pad number
|
|
*
|
|
* Return the format stored in the given configuration for an entity's pad. The
|
|
* configuration can be an ACTIVE or TRY configuration.
|
|
*/
|
|
struct v4l2_mbus_framefmt *
|
|
vsp1_entity_get_pad_format(struct vsp1_entity *entity,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
unsigned int pad)
|
|
{
|
|
return v4l2_subdev_get_try_format(&entity->subdev, cfg, pad);
|
|
}
|
|
|
|
/**
|
|
* vsp1_entity_get_pad_selection - Get a pad selection from storage for entity
|
|
* @entity: the entity
|
|
* @cfg: the configuration storage
|
|
* @pad: the pad number
|
|
* @target: the selection target
|
|
*
|
|
* Return the selection rectangle stored in the given configuration for an
|
|
* entity's pad. The configuration can be an ACTIVE or TRY configuration. The
|
|
* selection target can be COMPOSE or CROP.
|
|
*/
|
|
struct v4l2_rect *
|
|
vsp1_entity_get_pad_selection(struct vsp1_entity *entity,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
unsigned int pad, unsigned int target)
|
|
{
|
|
switch (target) {
|
|
case V4L2_SEL_TGT_COMPOSE:
|
|
return v4l2_subdev_get_try_compose(&entity->subdev, cfg, pad);
|
|
case V4L2_SEL_TGT_CROP:
|
|
return v4l2_subdev_get_try_crop(&entity->subdev, cfg, pad);
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* vsp1_entity_init_cfg - Initialize formats on all pads
|
|
* @subdev: V4L2 subdevice
|
|
* @cfg: V4L2 subdev pad configuration
|
|
*
|
|
* Initialize all pad formats with default values in the given pad config. This
|
|
* function can be used as a handler for the subdev pad::init_cfg operation.
|
|
*/
|
|
int vsp1_entity_init_cfg(struct v4l2_subdev *subdev,
|
|
struct v4l2_subdev_pad_config *cfg)
|
|
{
|
|
struct v4l2_subdev_format format;
|
|
unsigned int pad;
|
|
|
|
for (pad = 0; pad < subdev->entity.num_pads - 1; ++pad) {
|
|
memset(&format, 0, sizeof(format));
|
|
|
|
format.pad = pad;
|
|
format.which = cfg ? V4L2_SUBDEV_FORMAT_TRY
|
|
: V4L2_SUBDEV_FORMAT_ACTIVE;
|
|
|
|
v4l2_subdev_call(subdev, pad, set_fmt, cfg, &format);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vsp1_subdev_get_pad_format - Subdev pad get_fmt handler
|
|
* @subdev: V4L2 subdevice
|
|
* @cfg: V4L2 subdev pad configuration
|
|
* @fmt: V4L2 subdev format
|
|
*
|
|
* This function implements the subdev get_fmt pad operation. It can be used as
|
|
* a direct drop-in for the operation handler.
|
|
*/
|
|
int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_format *fmt)
|
|
{
|
|
struct vsp1_entity *entity = to_vsp1_entity(subdev);
|
|
struct v4l2_subdev_pad_config *config;
|
|
|
|
config = vsp1_entity_get_pad_config(entity, cfg, fmt->which);
|
|
if (!config)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&entity->lock);
|
|
fmt->format = *vsp1_entity_get_pad_format(entity, config, fmt->pad);
|
|
mutex_unlock(&entity->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vsp1_subdev_enum_mbus_code - Subdev pad enum_mbus_code handler
|
|
* @subdev: V4L2 subdevice
|
|
* @cfg: V4L2 subdev pad configuration
|
|
* @code: Media bus code enumeration
|
|
* @codes: Array of supported media bus codes
|
|
* @ncodes: Number of supported media bus codes
|
|
*
|
|
* This function implements the subdev enum_mbus_code pad operation for entities
|
|
* that do not support format conversion. It enumerates the given supported
|
|
* media bus codes on the sink pad and reports a source pad format identical to
|
|
* the sink pad.
|
|
*/
|
|
int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_mbus_code_enum *code,
|
|
const unsigned int *codes, unsigned int ncodes)
|
|
{
|
|
struct vsp1_entity *entity = to_vsp1_entity(subdev);
|
|
|
|
if (code->pad == 0) {
|
|
if (code->index >= ncodes)
|
|
return -EINVAL;
|
|
|
|
code->code = codes[code->index];
|
|
} else {
|
|
struct v4l2_subdev_pad_config *config;
|
|
struct v4l2_mbus_framefmt *format;
|
|
|
|
/*
|
|
* The entity can't perform format conversion, the sink format
|
|
* is always identical to the source format.
|
|
*/
|
|
if (code->index)
|
|
return -EINVAL;
|
|
|
|
config = vsp1_entity_get_pad_config(entity, cfg, code->which);
|
|
if (!config)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&entity->lock);
|
|
format = vsp1_entity_get_pad_format(entity, config, 0);
|
|
code->code = format->code;
|
|
mutex_unlock(&entity->lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* vsp1_subdev_enum_frame_size - Subdev pad enum_frame_size handler
|
|
* @subdev: V4L2 subdevice
|
|
* @cfg: V4L2 subdev pad configuration
|
|
* @fse: Frame size enumeration
|
|
* @min_width: Minimum image width
|
|
* @min_height: Minimum image height
|
|
* @max_width: Maximum image width
|
|
* @max_height: Maximum image height
|
|
*
|
|
* This function implements the subdev enum_frame_size pad operation for
|
|
* entities that do not support scaling or cropping. It reports the given
|
|
* minimum and maximum frame width and height on the sink pad, and a fixed
|
|
* source pad size identical to the sink pad.
|
|
*/
|
|
int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_frame_size_enum *fse,
|
|
unsigned int min_width, unsigned int min_height,
|
|
unsigned int max_width, unsigned int max_height)
|
|
{
|
|
struct vsp1_entity *entity = to_vsp1_entity(subdev);
|
|
struct v4l2_subdev_pad_config *config;
|
|
struct v4l2_mbus_framefmt *format;
|
|
int ret = 0;
|
|
|
|
config = vsp1_entity_get_pad_config(entity, cfg, fse->which);
|
|
if (!config)
|
|
return -EINVAL;
|
|
|
|
format = vsp1_entity_get_pad_format(entity, config, fse->pad);
|
|
|
|
mutex_lock(&entity->lock);
|
|
|
|
if (fse->index || fse->code != format->code) {
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
if (fse->pad == 0) {
|
|
fse->min_width = min_width;
|
|
fse->max_width = max_width;
|
|
fse->min_height = min_height;
|
|
fse->max_height = max_height;
|
|
} else {
|
|
/*
|
|
* The size on the source pad are fixed and always identical to
|
|
* the size on the sink pad.
|
|
*/
|
|
fse->min_width = format->width;
|
|
fse->max_width = format->width;
|
|
fse->min_height = format->height;
|
|
fse->max_height = format->height;
|
|
}
|
|
|
|
done:
|
|
mutex_unlock(&entity->lock);
|
|
return ret;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Media Operations
|
|
*/
|
|
|
|
static inline struct vsp1_entity *
|
|
media_entity_to_vsp1_entity(struct media_entity *entity)
|
|
{
|
|
return container_of(entity, struct vsp1_entity, subdev.entity);
|
|
}
|
|
|
|
static int vsp1_entity_link_setup_source(const struct media_pad *source_pad,
|
|
const struct media_pad *sink_pad,
|
|
u32 flags)
|
|
{
|
|
struct vsp1_entity *source;
|
|
|
|
source = media_entity_to_vsp1_entity(source_pad->entity);
|
|
|
|
if (!source->route)
|
|
return 0;
|
|
|
|
if (flags & MEDIA_LNK_FL_ENABLED) {
|
|
struct vsp1_entity *sink
|
|
= media_entity_to_vsp1_entity(sink_pad->entity);
|
|
|
|
/*
|
|
* Fan-out is limited to one for the normal data path plus
|
|
* optional HGO and HGT. We ignore the HGO and HGT here.
|
|
*/
|
|
if (sink->type != VSP1_ENTITY_HGO &&
|
|
sink->type != VSP1_ENTITY_HGT) {
|
|
if (source->sink)
|
|
return -EBUSY;
|
|
source->sink = sink;
|
|
source->sink_pad = sink_pad->index;
|
|
}
|
|
} else {
|
|
source->sink = NULL;
|
|
source->sink_pad = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vsp1_entity_link_setup_sink(const struct media_pad *source_pad,
|
|
const struct media_pad *sink_pad,
|
|
u32 flags)
|
|
{
|
|
struct vsp1_entity *sink;
|
|
struct vsp1_entity *source;
|
|
|
|
sink = media_entity_to_vsp1_entity(sink_pad->entity);
|
|
source = media_entity_to_vsp1_entity(source_pad->entity);
|
|
|
|
if (flags & MEDIA_LNK_FL_ENABLED) {
|
|
/* Fan-in is limited to one. */
|
|
if (sink->sources[sink_pad->index])
|
|
return -EBUSY;
|
|
|
|
sink->sources[sink_pad->index] = source;
|
|
} else {
|
|
sink->sources[sink_pad->index] = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int vsp1_entity_link_setup(struct media_entity *entity,
|
|
const struct media_pad *local,
|
|
const struct media_pad *remote, u32 flags)
|
|
{
|
|
if (local->flags & MEDIA_PAD_FL_SOURCE)
|
|
return vsp1_entity_link_setup_source(local, remote, flags);
|
|
else
|
|
return vsp1_entity_link_setup_sink(remote, local, flags);
|
|
}
|
|
|
|
/**
|
|
* vsp1_entity_remote_pad - Find the pad at the remote end of a link
|
|
* @pad: Pad at the local end of the link
|
|
*
|
|
* Search for a remote pad connected to the given pad by iterating over all
|
|
* links originating or terminating at that pad until an enabled link is found.
|
|
*
|
|
* Our link setup implementation guarantees that the output fan-out will not be
|
|
* higher than one for the data pipelines, except for the links to the HGO and
|
|
* HGT that can be enabled in addition to a regular data link. When traversing
|
|
* outgoing links this function ignores HGO and HGT entities and should thus be
|
|
* used in place of the generic media_entity_remote_pad() function to traverse
|
|
* data pipelines.
|
|
*
|
|
* Return a pointer to the pad at the remote end of the first found enabled
|
|
* link, or NULL if no enabled link has been found.
|
|
*/
|
|
struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad)
|
|
{
|
|
struct media_link *link;
|
|
|
|
list_for_each_entry(link, &pad->entity->links, list) {
|
|
struct vsp1_entity *entity;
|
|
|
|
if (!(link->flags & MEDIA_LNK_FL_ENABLED))
|
|
continue;
|
|
|
|
/* If we're the sink the source will never be an HGO or HGT. */
|
|
if (link->sink == pad)
|
|
return link->source;
|
|
|
|
if (link->source != pad)
|
|
continue;
|
|
|
|
/* If the sink isn't a subdevice it can't be an HGO or HGT. */
|
|
if (!is_media_entity_v4l2_subdev(link->sink->entity))
|
|
return link->sink;
|
|
|
|
entity = media_entity_to_vsp1_entity(link->sink->entity);
|
|
if (entity->type != VSP1_ENTITY_HGO &&
|
|
entity->type != VSP1_ENTITY_HGT)
|
|
return link->sink;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Initialization
|
|
*/
|
|
|
|
#define VSP1_ENTITY_ROUTE(ent) \
|
|
{ VSP1_ENTITY_##ent, 0, VI6_DPR_##ent##_ROUTE, \
|
|
{ VI6_DPR_NODE_##ent }, VI6_DPR_NODE_##ent }
|
|
|
|
#define VSP1_ENTITY_ROUTE_RPF(idx) \
|
|
{ VSP1_ENTITY_RPF, idx, VI6_DPR_RPF_ROUTE(idx), \
|
|
{ 0, }, VI6_DPR_NODE_RPF(idx) }
|
|
|
|
#define VSP1_ENTITY_ROUTE_UDS(idx) \
|
|
{ VSP1_ENTITY_UDS, idx, VI6_DPR_UDS_ROUTE(idx), \
|
|
{ VI6_DPR_NODE_UDS(idx) }, VI6_DPR_NODE_UDS(idx) }
|
|
|
|
#define VSP1_ENTITY_ROUTE_WPF(idx) \
|
|
{ VSP1_ENTITY_WPF, idx, 0, \
|
|
{ VI6_DPR_NODE_WPF(idx) }, VI6_DPR_NODE_WPF(idx) }
|
|
|
|
static const struct vsp1_route vsp1_routes[] = {
|
|
{ VSP1_ENTITY_BRS, 0, VI6_DPR_ILV_BRS_ROUTE,
|
|
{ VI6_DPR_NODE_BRS_IN(0), VI6_DPR_NODE_BRS_IN(1) }, 0 },
|
|
{ VSP1_ENTITY_BRU, 0, VI6_DPR_BRU_ROUTE,
|
|
{ VI6_DPR_NODE_BRU_IN(0), VI6_DPR_NODE_BRU_IN(1),
|
|
VI6_DPR_NODE_BRU_IN(2), VI6_DPR_NODE_BRU_IN(3),
|
|
VI6_DPR_NODE_BRU_IN(4) }, VI6_DPR_NODE_BRU_OUT },
|
|
VSP1_ENTITY_ROUTE(CLU),
|
|
{ VSP1_ENTITY_HGO, 0, 0, { 0, }, 0 },
|
|
{ VSP1_ENTITY_HGT, 0, 0, { 0, }, 0 },
|
|
VSP1_ENTITY_ROUTE(HSI),
|
|
VSP1_ENTITY_ROUTE(HST),
|
|
{ VSP1_ENTITY_LIF, 0, 0, { 0, }, 0 },
|
|
{ VSP1_ENTITY_LIF, 1, 0, { 0, }, 0 },
|
|
VSP1_ENTITY_ROUTE(LUT),
|
|
VSP1_ENTITY_ROUTE_RPF(0),
|
|
VSP1_ENTITY_ROUTE_RPF(1),
|
|
VSP1_ENTITY_ROUTE_RPF(2),
|
|
VSP1_ENTITY_ROUTE_RPF(3),
|
|
VSP1_ENTITY_ROUTE_RPF(4),
|
|
VSP1_ENTITY_ROUTE(SRU),
|
|
VSP1_ENTITY_ROUTE_UDS(0),
|
|
VSP1_ENTITY_ROUTE_UDS(1),
|
|
VSP1_ENTITY_ROUTE_UDS(2),
|
|
VSP1_ENTITY_ROUTE_WPF(0),
|
|
VSP1_ENTITY_ROUTE_WPF(1),
|
|
VSP1_ENTITY_ROUTE_WPF(2),
|
|
VSP1_ENTITY_ROUTE_WPF(3),
|
|
};
|
|
|
|
int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity,
|
|
const char *name, unsigned int num_pads,
|
|
const struct v4l2_subdev_ops *ops, u32 function)
|
|
{
|
|
struct v4l2_subdev *subdev;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vsp1_routes); ++i) {
|
|
if (vsp1_routes[i].type == entity->type &&
|
|
vsp1_routes[i].index == entity->index) {
|
|
entity->route = &vsp1_routes[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(vsp1_routes))
|
|
return -EINVAL;
|
|
|
|
mutex_init(&entity->lock);
|
|
|
|
entity->vsp1 = vsp1;
|
|
entity->source_pad = num_pads - 1;
|
|
|
|
/* Allocate and initialize pads. */
|
|
entity->pads = devm_kzalloc(vsp1->dev, num_pads * sizeof(*entity->pads),
|
|
GFP_KERNEL);
|
|
if (entity->pads == NULL)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num_pads - 1; ++i)
|
|
entity->pads[i].flags = MEDIA_PAD_FL_SINK;
|
|
|
|
entity->sources = devm_kcalloc(vsp1->dev, max(num_pads - 1, 1U),
|
|
sizeof(*entity->sources), GFP_KERNEL);
|
|
if (entity->sources == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Single-pad entities only have a sink. */
|
|
entity->pads[num_pads - 1].flags = num_pads > 1 ? MEDIA_PAD_FL_SOURCE
|
|
: MEDIA_PAD_FL_SINK;
|
|
|
|
/* Initialize the media entity. */
|
|
ret = media_entity_pads_init(&entity->subdev.entity, num_pads,
|
|
entity->pads);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Initialize the V4L2 subdev. */
|
|
subdev = &entity->subdev;
|
|
v4l2_subdev_init(subdev, ops);
|
|
|
|
subdev->entity.function = function;
|
|
subdev->entity.ops = &vsp1->media_ops;
|
|
subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
|
|
snprintf(subdev->name, sizeof(subdev->name), "%s %s",
|
|
dev_name(vsp1->dev), name);
|
|
|
|
vsp1_entity_init_cfg(subdev, NULL);
|
|
|
|
/*
|
|
* Allocate the pad configuration to store formats and selection
|
|
* rectangles.
|
|
*/
|
|
entity->config = v4l2_subdev_alloc_pad_config(&entity->subdev);
|
|
if (entity->config == NULL) {
|
|
media_entity_cleanup(&entity->subdev.entity);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void vsp1_entity_destroy(struct vsp1_entity *entity)
|
|
{
|
|
if (entity->ops && entity->ops->destroy)
|
|
entity->ops->destroy(entity);
|
|
if (entity->subdev.ctrl_handler)
|
|
v4l2_ctrl_handler_free(entity->subdev.ctrl_handler);
|
|
v4l2_subdev_free_pad_config(entity->config);
|
|
media_entity_cleanup(&entity->subdev.entity);
|
|
}
|