1 //SPDX-License-Identifier: GPL-2.0
3 * Xilinx Scene Change Detection driver
5 * Copyright (C) 2018 Xilinx, Inc.
7 * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
8 * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
11 #include <linux/clk.h>
12 #include <linux/delay.h>
13 #include <linux/dmaengine.h>
14 #include <linux/module.h>
15 #include <linux/gpio/consumer.h>
16 #include <linux/interrupt.h>
17 #include <linux/kernel.h>
18 #include <linux/module.h>
20 #include <linux/of_irq.h>
21 #include <linux/platform_device.h>
22 #include <linux/slab.h>
23 #include <linux/xilinx-v4l2-controls.h>
24 #include <linux/xilinx-v4l2-events.h>
25 #include <media/v4l2-async.h>
26 #include <media/v4l2-ctrls.h>
27 #include <media/v4l2-device.h>
28 #include <media/v4l2-event.h>
29 #include <media/v4l2-subdev.h>
31 #include "xilinx-scenechange.h"
32 #include "xilinx-vip.h"
34 #define XSCD_MAX_WIDTH 3840
35 #define XSCD_MAX_HEIGHT 2160
36 #define XSCD_MIN_WIDTH 640
37 #define XSCD_MIN_HEIGHT 480
39 #define XSCD_V_SUBSAMPLING 16
40 #define XSCD_BYTE_ALIGN 16
41 #define MULTIPLICATION_FACTOR 100
42 #define SCENE_CHANGE_THRESHOLD 0.5
44 #define XSCD_SCENE_CHANGE 1
45 #define XSCD_NO_SCENE_CHANGE 0
47 /* -----------------------------------------------------------------------------
48 * V4L2 Subdevice Pad Operations
51 static int xscd_enum_mbus_code(struct v4l2_subdev *subdev,
52 struct v4l2_subdev_pad_config *cfg,
53 struct v4l2_subdev_mbus_code_enum *code)
58 static int xscd_enum_frame_size(struct v4l2_subdev *subdev,
59 struct v4l2_subdev_pad_config *cfg,
60 struct v4l2_subdev_frame_size_enum *fse)
65 static struct v4l2_mbus_framefmt *
66 __xscd_get_pad_format(struct xscd_chan *chan,
67 struct v4l2_subdev_pad_config *cfg,
68 unsigned int pad, u32 which)
71 case V4L2_SUBDEV_FORMAT_TRY:
72 return v4l2_subdev_get_try_format(&chan->subdev, cfg, pad);
73 case V4L2_SUBDEV_FORMAT_ACTIVE:
81 static int xscd_get_format(struct v4l2_subdev *subdev,
82 struct v4l2_subdev_pad_config *cfg,
83 struct v4l2_subdev_format *fmt)
85 struct xscd_chan *chan = to_xscd_chan(subdev);
87 fmt->format = *__xscd_get_pad_format(chan, cfg, fmt->pad, fmt->which);
91 static int xscd_set_format(struct v4l2_subdev *subdev,
92 struct v4l2_subdev_pad_config *cfg,
93 struct v4l2_subdev_format *fmt)
95 struct xscd_chan *chan = to_xscd_chan(subdev);
96 struct v4l2_mbus_framefmt *format;
98 format = __xscd_get_pad_format(chan, cfg, fmt->pad, fmt->which);
99 format->width = clamp_t(unsigned int, fmt->format.width,
100 XSCD_MIN_WIDTH, XSCD_MAX_WIDTH);
101 format->height = clamp_t(unsigned int, fmt->format.height,
102 XSCD_MIN_HEIGHT, XSCD_MAX_HEIGHT);
103 format->code = fmt->format.code;
104 fmt->format = *format;
109 static int xscd_chan_get_vid_fmt(u32 media_bus_fmt, bool memory_based)
114 switch (media_bus_fmt) {
115 case MEDIA_BUS_FMT_VYYUYY8_1X24:
116 case MEDIA_BUS_FMT_UYVY8_1X16:
117 case MEDIA_BUS_FMT_VUY8_1X24:
118 vid_fmt = XSCD_VID_FMT_Y8;
120 case MEDIA_BUS_FMT_VYYUYY10_4X20:
121 case MEDIA_BUS_FMT_UYVY10_1X20:
122 case MEDIA_BUS_FMT_VUY10_1X30:
123 vid_fmt = XSCD_VID_FMT_Y10;
126 vid_fmt = XSCD_VID_FMT_Y8;
132 /* Streaming based */
133 switch (media_bus_fmt) {
134 case MEDIA_BUS_FMT_VYYUYY8_1X24:
135 case MEDIA_BUS_FMT_VYYUYY10_4X20:
136 vid_fmt = XSCD_VID_FMT_YUV_420;
138 case MEDIA_BUS_FMT_UYVY8_1X16:
139 case MEDIA_BUS_FMT_UYVY10_1X20:
140 vid_fmt = XSCD_VID_FMT_YUV_422;
142 case MEDIA_BUS_FMT_VUY8_1X24:
143 case MEDIA_BUS_FMT_VUY10_1X30:
144 vid_fmt = XSCD_VID_FMT_YUV_444;
146 case MEDIA_BUS_FMT_RBG888_1X24:
147 case MEDIA_BUS_FMT_RBG101010_1X30:
148 vid_fmt = XSCD_VID_FMT_RGB;
151 vid_fmt = XSCD_VID_FMT_YUV_420;
158 * xscd_chan_configure_params - Program parameters to HW registers
159 * @chan: Driver specific channel struct pointer
160 * @chan_offset: Register offset for a channel
162 static void xscd_chan_configure_params(struct xscd_chan *chan,
167 xscd_write(chan->iomem, XSCD_WIDTH_OFFSET + chan_offset,
170 /* Stride is required only for memory based IP, not for streaming IP */
171 if (chan->xscd->memory_based) {
172 stride = roundup(chan->format.width, XSCD_BYTE_ALIGN);
173 xscd_write(chan->iomem, XSCD_STRIDE_OFFSET + chan_offset,
177 xscd_write(chan->iomem, XSCD_HEIGHT_OFFSET + chan_offset,
178 chan->format.height);
180 /* Hardware video format */
181 vid_fmt = xscd_chan_get_vid_fmt(chan->format.code,
182 chan->xscd->memory_based);
183 xscd_write(chan->iomem, XSCD_VID_FMT_OFFSET + chan_offset, vid_fmt);
186 * This is the vertical subsampling factor of the input image. Instead
187 * of sampling every line to calculate the histogram, IP uses this
188 * register value to sample only specific lines of the frame.
190 xscd_write(chan->iomem, XSCD_SUBSAMPLE_OFFSET + chan_offset,
194 /* -----------------------------------------------------------------------------
195 * V4L2 Subdevice Operations
197 static int xscd_s_stream(struct v4l2_subdev *subdev, int enable)
199 struct xscd_chan *chan = to_xscd_chan(subdev);
203 /* TODO: Re-organise shared data in a better way */
204 chan->dmachan.en = enable;
206 spin_lock_irqsave(&chan->dmachan.lock, flags);
208 if (chan->xscd->memory_based) {
209 chan_offset = chan->id * XSCD_CHAN_OFFSET;
210 xscd_chan_configure_params(chan, chan_offset);
212 if (!chan->xscd->active_streams) {
213 chan->dmachan.valid_interrupt = true;
214 chan->xscd->active_streams++;
215 xscd_dma_start_transfer(&chan->dmachan);
216 xscd_dma_reset(&chan->dmachan);
217 xscd_dma_chan_enable(&chan->dmachan,
219 xscd_dma_start(&chan->dmachan);
221 chan->xscd->active_streams++;
224 chan->xscd->active_streams--;
227 /* Streaming based */
229 xscd_chan_configure_params(chan, chan->id);
230 xscd_dma_reset(&chan->dmachan);
231 xscd_dma_chan_enable(&chan->dmachan, BIT(chan->id));
232 xscd_dma_start(&chan->dmachan);
234 xscd_dma_halt(&chan->dmachan);
238 spin_unlock_irqrestore(&chan->dmachan.lock, flags);
242 static int xscd_subscribe_event(struct v4l2_subdev *sd,
244 struct v4l2_event_subscription *sub)
247 struct xscd_chan *chan = to_xscd_chan(sd);
249 mutex_lock(&chan->lock);
252 case V4L2_EVENT_XLNXSCD:
253 ret = v4l2_event_subscribe(fh, sub, 1, NULL);
259 mutex_unlock(&chan->lock);
264 static int xscd_unsubscribe_event(struct v4l2_subdev *sd,
266 struct v4l2_event_subscription *sub)
269 struct xscd_chan *chan = to_xscd_chan(sd);
271 mutex_lock(&chan->lock);
272 ret = v4l2_event_unsubscribe(fh, sub);
273 mutex_unlock(&chan->lock);
278 static int xscd_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
283 static int xscd_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
288 static const struct v4l2_subdev_core_ops xscd_core_ops = {
289 .subscribe_event = xscd_subscribe_event,
290 .unsubscribe_event = xscd_unsubscribe_event
293 static struct v4l2_subdev_video_ops xscd_video_ops = {
294 .s_stream = xscd_s_stream,
297 static struct v4l2_subdev_pad_ops xscd_pad_ops = {
298 .enum_mbus_code = xscd_enum_mbus_code,
299 .enum_frame_size = xscd_enum_frame_size,
300 .get_fmt = xscd_get_format,
301 .set_fmt = xscd_set_format,
304 static struct v4l2_subdev_ops xscd_ops = {
305 .core = &xscd_core_ops,
306 .video = &xscd_video_ops,
307 .pad = &xscd_pad_ops,
310 static const struct v4l2_subdev_internal_ops xscd_internal_ops = {
315 /* -----------------------------------------------------------------------------
319 static const struct media_entity_operations xscd_media_ops = {
320 .link_validate = v4l2_subdev_link_validate,
323 static void xscd_event_notify(struct xscd_chan *chan)
326 u32 sad, scd_threshold;
328 sad = xscd_read(chan->iomem, XSCD_SAD_OFFSET +
329 (chan->id * XSCD_CHAN_OFFSET));
330 sad = (sad * XSCD_V_SUBSAMPLING * MULTIPLICATION_FACTOR) /
331 (chan->format.width * chan->format.height);
332 eventdata = (u32 *)&chan->event.u.data;
333 scd_threshold = SCENE_CHANGE_THRESHOLD * MULTIPLICATION_FACTOR;
335 if (sad > scd_threshold)
336 eventdata[0] = XSCD_SCENE_CHANGE;
338 eventdata[0] = XSCD_NO_SCENE_CHANGE;
340 chan->event.type = V4L2_EVENT_XLNXSCD;
341 v4l2_subdev_notify_event(&chan->subdev, &chan->event);
344 void xscd_chan_irq_handler(struct xscd_chan *chan)
346 spin_lock(&chan->dmachan.lock);
348 if ((chan->xscd->memory_based && chan->dmachan.valid_interrupt) ||
349 !chan->xscd->memory_based) {
350 spin_unlock(&chan->dmachan.lock);
351 xscd_event_notify(chan);
355 spin_unlock(&chan->dmachan.lock);
359 * xscd_chan_init - Initialize the V4L2 subdev for a channel
360 * @xscd: Pointer to the SCD device structure
361 * @chan_id: Channel id
364 * Return: '0' on success and failure value on error
366 int xscd_chan_init(struct xscd_device *xscd, unsigned int chan_id,
367 struct device_node *node)
369 struct xscd_chan *chan;
370 struct v4l2_subdev *subdev;
374 chan = devm_kzalloc(xscd->dev, sizeof(*chan), GFP_KERNEL);
378 xscd->chans[chan_id] = chan;
380 mutex_init(&chan->lock);
383 chan->iomem = chan->xscd->iomem;
384 chan->dmachan.id = chan->id;
385 chan->dmachan.iomem = chan->xscd->iomem;
387 xscd->channels[chan->id] = &chan->dmachan;
389 /* Initialize V4L2 subdevice and media entity */
390 subdev = &chan->subdev;
391 v4l2_subdev_init(subdev, &xscd_ops);
392 subdev->dev = chan->xscd->dev;
393 subdev->fwnode = of_fwnode_handle(node);
394 subdev->internal_ops = &xscd_internal_ops;
395 snprintf(subdev->name, sizeof(subdev->name), "xlnx-scdchan.%u",
397 v4l2_set_subdevdata(subdev, chan);
398 subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
400 /* Initialize default format */
401 chan->format.code = MEDIA_BUS_FMT_VYYUYY8_1X24;
402 chan->format.field = V4L2_FIELD_NONE;
403 chan->format.width = XSCD_MAX_WIDTH;
404 chan->format.height = XSCD_MAX_HEIGHT;
406 /* Initialize media pads */
407 num_pads = xscd->memory_based ? 1 : 2;
408 chan->pad = devm_kzalloc(chan->xscd->dev,
409 sizeof(struct media_pad) * num_pads,
414 chan->pad[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
415 if (!xscd->memory_based)
416 chan->pad[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
418 ret = media_entity_pads_init(&subdev->entity, num_pads, chan->pad);
422 subdev->entity.ops = &xscd_media_ops;
423 ret = v4l2_async_register_subdev(subdev);
425 dev_err(chan->xscd->dev, "failed to register subdev\n");
429 dev_info(chan->xscd->dev, "Scene change detection channel found!\n");
433 media_entity_cleanup(&subdev->entity);