]> rtime.felk.cvut.cz Git - zynq/linux.git/blob - drivers/media/platform/xilinx/xilinx-scenechange-channel.c
v4l: xilinx: scd: Refactor the xscd_device structure
[zynq/linux.git] / drivers / media / platform / xilinx / xilinx-scenechange-channel.c
1 //SPDX-License-Identifier: GPL-2.0
2 /*
3  * Xilinx Scene Change Detection driver
4  *
5  * Copyright (C) 2018 Xilinx, Inc.
6  *
7  * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
8  *          Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
9  */
10
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>
19 #include <linux/of.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>
30
31 #include "xilinx-scenechange.h"
32 #include "xilinx-vip.h"
33
34 #define XSCD_MAX_WIDTH          3840
35 #define XSCD_MAX_HEIGHT         2160
36 #define XSCD_MIN_WIDTH          640
37 #define XSCD_MIN_HEIGHT         480
38
39 #define XSCD_V_SUBSAMPLING              16
40 #define XSCD_BYTE_ALIGN                 16
41 #define MULTIPLICATION_FACTOR           100
42 #define SCENE_CHANGE_THRESHOLD          0.5
43
44 #define XSCD_SCENE_CHANGE               1
45 #define XSCD_NO_SCENE_CHANGE            0
46
47 /* -----------------------------------------------------------------------------
48  * V4L2 Subdevice Pad Operations
49  */
50
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)
54 {
55         return 0;
56 }
57
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)
61 {
62         return 0;
63 }
64
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)
69 {
70         switch (which) {
71         case V4L2_SUBDEV_FORMAT_TRY:
72                 return v4l2_subdev_get_try_format(&chan->subdev, cfg, pad);
73         case V4L2_SUBDEV_FORMAT_ACTIVE:
74                 return &chan->format;
75         default:
76                 return NULL;
77         }
78         return NULL;
79 }
80
81 static int xscd_get_format(struct v4l2_subdev *subdev,
82                            struct v4l2_subdev_pad_config *cfg,
83                            struct v4l2_subdev_format *fmt)
84 {
85         struct xscd_chan *chan = to_xscd_chan(subdev);
86
87         fmt->format = *__xscd_get_pad_format(chan, cfg, fmt->pad, fmt->which);
88         return 0;
89 }
90
91 static int xscd_set_format(struct v4l2_subdev *subdev,
92                            struct v4l2_subdev_pad_config *cfg,
93                            struct v4l2_subdev_format *fmt)
94 {
95         struct xscd_chan *chan = to_xscd_chan(subdev);
96         struct v4l2_mbus_framefmt *format;
97
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;
105
106         return 0;
107 }
108
109 static int xscd_chan_get_vid_fmt(u32 media_bus_fmt, bool memory_based)
110 {
111         u32 vid_fmt;
112
113         if (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;
119                         break;
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;
124                         break;
125                 default:
126                         vid_fmt = XSCD_VID_FMT_Y8;
127                 }
128
129                 return vid_fmt;
130         }
131
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;
137                 break;
138         case MEDIA_BUS_FMT_UYVY8_1X16:
139         case MEDIA_BUS_FMT_UYVY10_1X20:
140                 vid_fmt = XSCD_VID_FMT_YUV_422;
141                 break;
142         case MEDIA_BUS_FMT_VUY8_1X24:
143         case MEDIA_BUS_FMT_VUY10_1X30:
144                 vid_fmt = XSCD_VID_FMT_YUV_444;
145                 break;
146         case MEDIA_BUS_FMT_RBG888_1X24:
147         case MEDIA_BUS_FMT_RBG101010_1X30:
148                 vid_fmt = XSCD_VID_FMT_RGB;
149                 break;
150         default:
151                 vid_fmt = XSCD_VID_FMT_YUV_420;
152         }
153
154         return vid_fmt;
155 }
156
157 /**
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
161  */
162 static void xscd_chan_configure_params(struct xscd_chan *chan,
163                                        u32 chan_offset)
164 {
165         u32 vid_fmt, stride;
166
167         xscd_write(chan->iomem, XSCD_WIDTH_OFFSET + chan_offset,
168                    chan->format.width);
169
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,
174                            stride);
175         }
176
177         xscd_write(chan->iomem, XSCD_HEIGHT_OFFSET + chan_offset,
178                    chan->format.height);
179
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);
184
185         /*
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.
189          */
190         xscd_write(chan->iomem, XSCD_SUBSAMPLE_OFFSET + chan_offset,
191                    XSCD_V_SUBSAMPLING);
192 }
193
194 /* -----------------------------------------------------------------------------
195  * V4L2 Subdevice Operations
196  */
197 static int xscd_s_stream(struct v4l2_subdev *subdev, int enable)
198 {
199         struct xscd_chan *chan = to_xscd_chan(subdev);
200         unsigned long flags;
201         u32 chan_offset;
202
203         /* TODO: Re-organise shared data in a better way */
204         chan->dmachan.en = enable;
205
206         spin_lock_irqsave(&chan->dmachan.lock, flags);
207
208         if (chan->xscd->memory_based) {
209                 chan_offset = chan->id * XSCD_CHAN_OFFSET;
210                 xscd_chan_configure_params(chan, chan_offset);
211                 if (enable) {
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,
218                                                      BIT(chan->id));
219                                 xscd_dma_start(&chan->dmachan);
220                         } else {
221                                 chan->xscd->active_streams++;
222                         }
223                 } else {
224                         chan->xscd->active_streams--;
225                 }
226         } else {
227                 /* Streaming based */
228                 if (enable) {
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);
233                 } else {
234                         xscd_dma_halt(&chan->dmachan);
235                 }
236         }
237
238         spin_unlock_irqrestore(&chan->dmachan.lock, flags);
239         return 0;
240 }
241
242 static int xscd_subscribe_event(struct v4l2_subdev *sd,
243                                 struct v4l2_fh *fh,
244                                 struct v4l2_event_subscription *sub)
245 {
246         int ret;
247         struct xscd_chan *chan = to_xscd_chan(sd);
248
249         mutex_lock(&chan->lock);
250
251         switch (sub->type) {
252         case V4L2_EVENT_XLNXSCD:
253                 ret = v4l2_event_subscribe(fh, sub, 1, NULL);
254                 break;
255         default:
256                 ret = -EINVAL;
257         }
258
259         mutex_unlock(&chan->lock);
260
261         return ret;
262 }
263
264 static int xscd_unsubscribe_event(struct v4l2_subdev *sd,
265                                   struct v4l2_fh *fh,
266                                   struct v4l2_event_subscription *sub)
267 {
268         int ret;
269         struct xscd_chan *chan = to_xscd_chan(sd);
270
271         mutex_lock(&chan->lock);
272         ret = v4l2_event_unsubscribe(fh, sub);
273         mutex_unlock(&chan->lock);
274
275         return ret;
276 }
277
278 static int xscd_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
279 {
280         return 0;
281 }
282
283 static int xscd_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
284 {
285         return 0;
286 }
287
288 static const struct v4l2_subdev_core_ops xscd_core_ops = {
289         .subscribe_event = xscd_subscribe_event,
290         .unsubscribe_event = xscd_unsubscribe_event
291 };
292
293 static struct v4l2_subdev_video_ops xscd_video_ops = {
294         .s_stream = xscd_s_stream,
295 };
296
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,
302 };
303
304 static struct v4l2_subdev_ops xscd_ops = {
305         .core = &xscd_core_ops,
306         .video = &xscd_video_ops,
307         .pad = &xscd_pad_ops,
308 };
309
310 static const struct v4l2_subdev_internal_ops xscd_internal_ops = {
311         .open = xscd_open,
312         .close = xscd_close,
313 };
314
315 /* -----------------------------------------------------------------------------
316  * Media Operations
317  */
318
319 static const struct media_entity_operations xscd_media_ops = {
320         .link_validate = v4l2_subdev_link_validate,
321 };
322
323 static void xscd_event_notify(struct xscd_chan *chan)
324 {
325         u32 *eventdata;
326         u32 sad, scd_threshold;
327
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;
334
335         if (sad > scd_threshold)
336                 eventdata[0] = XSCD_SCENE_CHANGE;
337         else
338                 eventdata[0] = XSCD_NO_SCENE_CHANGE;
339
340         chan->event.type = V4L2_EVENT_XLNXSCD;
341         v4l2_subdev_notify_event(&chan->subdev, &chan->event);
342 }
343
344 void xscd_chan_irq_handler(struct xscd_chan *chan)
345 {
346         spin_lock(&chan->dmachan.lock);
347
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);
352                 return;
353         }
354
355         spin_unlock(&chan->dmachan.lock);
356 }
357
358 /**
359  * xscd_chan_init - Initialize the V4L2 subdev for a channel
360  * @xscd: Pointer to the SCD device structure
361  * @chan_id: Channel id
362  * @node: device node
363  *
364  * Return: '0' on success and failure value on error
365  */
366 int xscd_chan_init(struct xscd_device *xscd, unsigned int chan_id,
367                    struct device_node *node)
368 {
369         struct xscd_chan *chan;
370         struct v4l2_subdev *subdev;
371         int ret;
372         u32 num_pads;
373
374         chan = devm_kzalloc(xscd->dev, sizeof(*chan), GFP_KERNEL);
375         if (!chan)
376                 return -ENOMEM;
377
378         xscd->chans[chan_id] = chan;
379
380         mutex_init(&chan->lock);
381         chan->xscd = xscd;
382         chan->id = chan_id;
383         chan->iomem = chan->xscd->iomem;
384         chan->dmachan.id = chan->id;
385         chan->dmachan.iomem = chan->xscd->iomem;
386
387         xscd->channels[chan->id] = &chan->dmachan;
388
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",
396                  chan_id);
397         v4l2_set_subdevdata(subdev, chan);
398         subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
399
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;
405
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,
410                                  GFP_KERNEL);
411         if (!chan->pad)
412                 return -ENOMEM;
413
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;
417
418         ret = media_entity_pads_init(&subdev->entity, num_pads, chan->pad);
419         if (ret < 0)
420                 goto error;
421
422         subdev->entity.ops = &xscd_media_ops;
423         ret = v4l2_async_register_subdev(subdev);
424         if (ret < 0) {
425                 dev_err(chan->xscd->dev, "failed to register subdev\n");
426                 goto error;
427         }
428
429         dev_info(chan->xscd->dev, "Scene change detection channel found!\n");
430         return 0;
431
432 error:
433         media_entity_cleanup(&subdev->entity);
434         return ret;
435 }