diff options
Diffstat (limited to 'drivers/media/platform/xilinx/xilinx-scenechange-channel.c')
-rw-r--r-- | drivers/media/platform/xilinx/xilinx-scenechange-channel.c | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/drivers/media/platform/xilinx/xilinx-scenechange-channel.c b/drivers/media/platform/xilinx/xilinx-scenechange-channel.c new file mode 100644 index 000000000000..852191ac6500 --- /dev/null +++ b/drivers/media/platform/xilinx/xilinx-scenechange-channel.c @@ -0,0 +1,352 @@ +//SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Scene Change Detection driver + * + * Copyright (C) 2018 Xilinx, Inc. + * + * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com> + * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com> + */ + +#include <linux/of.h> +#include <linux/xilinx-v4l2-events.h> + +#include <media/v4l2-async.h> +#include <media/v4l2-event.h> +#include <media/v4l2-subdev.h> + +#include "xilinx-scenechange.h" +#include "xilinx-vip.h" + +#define XSCD_MAX_WIDTH 3840 +#define XSCD_MAX_HEIGHT 2160 +#define XSCD_MIN_WIDTH 640 +#define XSCD_MIN_HEIGHT 480 + +#define XSCD_V_SUBSAMPLING 16 +#define XSCD_BYTE_ALIGN 16 +#define MULTIPLICATION_FACTOR 100 +#define SCENE_CHANGE_THRESHOLD 0.5 + +#define XSCD_SCENE_CHANGE 1 +#define XSCD_NO_SCENE_CHANGE 0 + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int xscd_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + return 0; +} + +static int xscd_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + return 0; +} + +static struct v4l2_mbus_framefmt * +__xscd_get_pad_format(struct xscd_chan *chan, + struct v4l2_subdev_pad_config *cfg, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&chan->subdev, cfg, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &chan->format; + default: + return NULL; + } + return NULL; +} + +static int xscd_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct xscd_chan *chan = to_xscd_chan(subdev); + + fmt->format = *__xscd_get_pad_format(chan, cfg, fmt->pad, fmt->which); + return 0; +} + +static int xscd_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct xscd_chan *chan = to_xscd_chan(subdev); + struct v4l2_mbus_framefmt *format; + + format = __xscd_get_pad_format(chan, cfg, fmt->pad, fmt->which); + format->width = clamp_t(unsigned int, fmt->format.width, + XSCD_MIN_WIDTH, XSCD_MAX_WIDTH); + format->height = clamp_t(unsigned int, fmt->format.height, + XSCD_MIN_HEIGHT, XSCD_MAX_HEIGHT); + format->code = fmt->format.code; + fmt->format = *format; + + return 0; +} + +static int xscd_chan_get_vid_fmt(u32 media_bus_fmt, bool memory_based) +{ + u32 vid_fmt; + + if (memory_based) { + switch (media_bus_fmt) { + case MEDIA_BUS_FMT_VYYUYY8_1X24: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VUY8_1X24: + vid_fmt = XSCD_VID_FMT_Y8; + break; + case MEDIA_BUS_FMT_VYYUYY10_4X20: + case MEDIA_BUS_FMT_UYVY10_1X20: + case MEDIA_BUS_FMT_VUY10_1X30: + vid_fmt = XSCD_VID_FMT_Y10; + break; + default: + vid_fmt = XSCD_VID_FMT_Y8; + } + + return vid_fmt; + } + + /* Streaming based */ + switch (media_bus_fmt) { + case MEDIA_BUS_FMT_VYYUYY8_1X24: + case MEDIA_BUS_FMT_VYYUYY10_4X20: + vid_fmt = XSCD_VID_FMT_YUV_420; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY10_1X20: + vid_fmt = XSCD_VID_FMT_YUV_422; + break; + case MEDIA_BUS_FMT_VUY8_1X24: + case MEDIA_BUS_FMT_VUY10_1X30: + vid_fmt = XSCD_VID_FMT_YUV_444; + break; + case MEDIA_BUS_FMT_RBG888_1X24: + case MEDIA_BUS_FMT_RBG101010_1X30: + vid_fmt = XSCD_VID_FMT_RGB; + break; + default: + vid_fmt = XSCD_VID_FMT_YUV_420; + } + + return vid_fmt; +} + +/** + * xscd_chan_configure_params - Program parameters to HW registers + * @chan: Driver specific channel struct pointer + */ +static void xscd_chan_configure_params(struct xscd_chan *chan) +{ + u32 vid_fmt, stride; + + xscd_write(chan->iomem, XSCD_WIDTH_OFFSET, chan->format.width); + + /* Stride is required only for memory based IP, not for streaming IP */ + if (chan->xscd->memory_based) { + stride = roundup(chan->format.width, XSCD_BYTE_ALIGN); + xscd_write(chan->iomem, XSCD_STRIDE_OFFSET, stride); + } + + xscd_write(chan->iomem, XSCD_HEIGHT_OFFSET, chan->format.height); + + /* Hardware video format */ + vid_fmt = xscd_chan_get_vid_fmt(chan->format.code, + chan->xscd->memory_based); + xscd_write(chan->iomem, XSCD_VID_FMT_OFFSET, vid_fmt); + + /* + * This is the vertical subsampling factor of the input image. Instead + * of sampling every line to calculate the histogram, IP uses this + * register value to sample only specific lines of the frame. + */ + xscd_write(chan->iomem, XSCD_SUBSAMPLE_OFFSET, XSCD_V_SUBSAMPLING); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ +static int xscd_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct xscd_chan *chan = to_xscd_chan(subdev); + + if (enable) + xscd_chan_configure_params(chan); + + xscd_dma_enable_channel(&chan->dmachan, enable); + return 0; +} + +static int xscd_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int ret; + struct xscd_chan *chan = to_xscd_chan(sd); + + mutex_lock(&chan->lock); + + switch (sub->type) { + case V4L2_EVENT_XLNXSCD: + ret = v4l2_event_subscribe(fh, sub, 1, NULL); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&chan->lock); + + return ret; +} + +static int xscd_unsubscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int ret; + struct xscd_chan *chan = to_xscd_chan(sd); + + mutex_lock(&chan->lock); + ret = v4l2_event_unsubscribe(fh, sub); + mutex_unlock(&chan->lock); + + return ret; +} + +static int xscd_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +static int xscd_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh) +{ + return 0; +} + +static const struct v4l2_subdev_core_ops xscd_core_ops = { + .subscribe_event = xscd_subscribe_event, + .unsubscribe_event = xscd_unsubscribe_event +}; + +static struct v4l2_subdev_video_ops xscd_video_ops = { + .s_stream = xscd_s_stream, +}; + +static struct v4l2_subdev_pad_ops xscd_pad_ops = { + .enum_mbus_code = xscd_enum_mbus_code, + .enum_frame_size = xscd_enum_frame_size, + .get_fmt = xscd_get_format, + .set_fmt = xscd_set_format, +}; + +static struct v4l2_subdev_ops xscd_ops = { + .core = &xscd_core_ops, + .video = &xscd_video_ops, + .pad = &xscd_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops xscd_internal_ops = { + .open = xscd_open, + .close = xscd_close, +}; + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static const struct media_entity_operations xscd_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +void xscd_chan_event_notify(struct xscd_chan *chan) +{ + u32 *eventdata; + u32 sad, scd_threshold; + + sad = xscd_read(chan->iomem, XSCD_SAD_OFFSET); + sad = (sad * XSCD_V_SUBSAMPLING * MULTIPLICATION_FACTOR) / + (chan->format.width * chan->format.height); + eventdata = (u32 *)&chan->event.u.data; + scd_threshold = SCENE_CHANGE_THRESHOLD * MULTIPLICATION_FACTOR; + + if (sad > scd_threshold) + eventdata[0] = XSCD_SCENE_CHANGE; + else + eventdata[0] = XSCD_NO_SCENE_CHANGE; + + chan->event.type = V4L2_EVENT_XLNXSCD; + v4l2_subdev_notify_event(&chan->subdev, &chan->event); +} + +/** + * xscd_chan_init - Initialize the V4L2 subdev for a channel + * @xscd: Pointer to the SCD device structure + * @chan_id: Channel id + * @node: device node + * + * Return: '0' on success and failure value on error + */ +int xscd_chan_init(struct xscd_device *xscd, unsigned int chan_id, + struct device_node *node) +{ + struct xscd_chan *chan = &xscd->chans[chan_id]; + struct v4l2_subdev *subdev; + unsigned int num_pads; + int ret; + + mutex_init(&chan->lock); + chan->xscd = xscd; + chan->id = chan_id; + chan->iomem = chan->xscd->iomem + chan->id * XSCD_CHAN_OFFSET; + + /* Initialize V4L2 subdevice and media entity */ + subdev = &chan->subdev; + v4l2_subdev_init(subdev, &xscd_ops); + subdev->dev = chan->xscd->dev; + subdev->fwnode = of_fwnode_handle(node); + subdev->internal_ops = &xscd_internal_ops; + snprintf(subdev->name, sizeof(subdev->name), "xlnx-scdchan.%u", + chan_id); + v4l2_set_subdevdata(subdev, chan); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + + /* Initialize default format */ + chan->format.code = MEDIA_BUS_FMT_VYYUYY8_1X24; + chan->format.field = V4L2_FIELD_NONE; + chan->format.width = XSCD_MAX_WIDTH; + chan->format.height = XSCD_MAX_HEIGHT; + + /* Initialize media pads */ + num_pads = xscd->memory_based ? 1 : 2; + + chan->pads[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + if (!xscd->memory_based) + chan->pads[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&subdev->entity, num_pads, chan->pads); + if (ret < 0) + goto error; + + subdev->entity.ops = &xscd_media_ops; + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) { + dev_err(chan->xscd->dev, "failed to register subdev\n"); + goto error; + } + + dev_info(chan->xscd->dev, "Scene change detection channel found!\n"); + return 0; + +error: + media_entity_cleanup(&subdev->entity); + return ret; +} |