]> rtime.felk.cvut.cz Git - zynq/linux.git/blob - drivers/media/platform/xilinx/xilinx-scenechange-channel.c
v4l: xilinx: scd: Store channel registers address in iomem field
[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  */
161 static void xscd_chan_configure_params(struct xscd_chan *chan)
162 {
163         u32 vid_fmt, stride;
164
165         xscd_write(chan->iomem, XSCD_WIDTH_OFFSET, chan->format.width);
166
167         /* Stride is required only for memory based IP, not for streaming IP */
168         if (chan->xscd->memory_based) {
169                 stride = roundup(chan->format.width, XSCD_BYTE_ALIGN);
170                 xscd_write(chan->iomem, XSCD_STRIDE_OFFSET, stride);
171         }
172
173         xscd_write(chan->iomem, XSCD_HEIGHT_OFFSET, chan->format.height);
174
175         /* Hardware video format */
176         vid_fmt = xscd_chan_get_vid_fmt(chan->format.code,
177                                         chan->xscd->memory_based);
178         xscd_write(chan->iomem, XSCD_VID_FMT_OFFSET, vid_fmt);
179
180         /*
181          * This is the vertical subsampling factor of the input image. Instead
182          * of sampling every line to calculate the histogram, IP uses this
183          * register value to sample only specific lines of the frame.
184          */
185         xscd_write(chan->iomem, XSCD_SUBSAMPLE_OFFSET, XSCD_V_SUBSAMPLING);
186 }
187
188 /* -----------------------------------------------------------------------------
189  * V4L2 Subdevice Operations
190  */
191 static int xscd_s_stream(struct v4l2_subdev *subdev, int enable)
192 {
193         struct xscd_chan *chan = to_xscd_chan(subdev);
194         unsigned long flags;
195
196         /* TODO: Re-organise shared data in a better way */
197         chan->dmachan.en = enable;
198
199         spin_lock_irqsave(&chan->dmachan.lock, flags);
200
201         if (chan->xscd->memory_based) {
202                 xscd_chan_configure_params(chan);
203                 if (enable) {
204                         if (!chan->xscd->active_streams) {
205                                 chan->dmachan.valid_interrupt = true;
206                                 chan->xscd->active_streams++;
207                                 xscd_dma_start_transfer(&chan->dmachan);
208                                 xscd_dma_reset(&chan->dmachan);
209                                 xscd_dma_chan_enable(&chan->dmachan,
210                                                      BIT(chan->id));
211                                 xscd_dma_start(&chan->dmachan);
212                         } else {
213                                 chan->xscd->active_streams++;
214                         }
215                 } else {
216                         chan->xscd->active_streams--;
217                 }
218         } else {
219                 /* Streaming based */
220                 if (enable) {
221                         xscd_chan_configure_params(chan);
222                         xscd_dma_reset(&chan->dmachan);
223                         xscd_dma_chan_enable(&chan->dmachan, BIT(chan->id));
224                         xscd_dma_start(&chan->dmachan);
225                 } else {
226                         xscd_dma_halt(&chan->dmachan);
227                 }
228         }
229
230         spin_unlock_irqrestore(&chan->dmachan.lock, flags);
231         return 0;
232 }
233
234 static int xscd_subscribe_event(struct v4l2_subdev *sd,
235                                 struct v4l2_fh *fh,
236                                 struct v4l2_event_subscription *sub)
237 {
238         int ret;
239         struct xscd_chan *chan = to_xscd_chan(sd);
240
241         mutex_lock(&chan->lock);
242
243         switch (sub->type) {
244         case V4L2_EVENT_XLNXSCD:
245                 ret = v4l2_event_subscribe(fh, sub, 1, NULL);
246                 break;
247         default:
248                 ret = -EINVAL;
249         }
250
251         mutex_unlock(&chan->lock);
252
253         return ret;
254 }
255
256 static int xscd_unsubscribe_event(struct v4l2_subdev *sd,
257                                   struct v4l2_fh *fh,
258                                   struct v4l2_event_subscription *sub)
259 {
260         int ret;
261         struct xscd_chan *chan = to_xscd_chan(sd);
262
263         mutex_lock(&chan->lock);
264         ret = v4l2_event_unsubscribe(fh, sub);
265         mutex_unlock(&chan->lock);
266
267         return ret;
268 }
269
270 static int xscd_open(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
271 {
272         return 0;
273 }
274
275 static int xscd_close(struct v4l2_subdev *subdev, struct v4l2_subdev_fh *fh)
276 {
277         return 0;
278 }
279
280 static const struct v4l2_subdev_core_ops xscd_core_ops = {
281         .subscribe_event = xscd_subscribe_event,
282         .unsubscribe_event = xscd_unsubscribe_event
283 };
284
285 static struct v4l2_subdev_video_ops xscd_video_ops = {
286         .s_stream = xscd_s_stream,
287 };
288
289 static struct v4l2_subdev_pad_ops xscd_pad_ops = {
290         .enum_mbus_code = xscd_enum_mbus_code,
291         .enum_frame_size = xscd_enum_frame_size,
292         .get_fmt = xscd_get_format,
293         .set_fmt = xscd_set_format,
294 };
295
296 static struct v4l2_subdev_ops xscd_ops = {
297         .core = &xscd_core_ops,
298         .video = &xscd_video_ops,
299         .pad = &xscd_pad_ops,
300 };
301
302 static const struct v4l2_subdev_internal_ops xscd_internal_ops = {
303         .open = xscd_open,
304         .close = xscd_close,
305 };
306
307 /* -----------------------------------------------------------------------------
308  * Media Operations
309  */
310
311 static const struct media_entity_operations xscd_media_ops = {
312         .link_validate = v4l2_subdev_link_validate,
313 };
314
315 static void xscd_event_notify(struct xscd_chan *chan)
316 {
317         u32 *eventdata;
318         u32 sad, scd_threshold;
319
320         sad = xscd_read(chan->iomem, XSCD_SAD_OFFSET);
321         sad = (sad * XSCD_V_SUBSAMPLING * MULTIPLICATION_FACTOR) /
322                (chan->format.width * chan->format.height);
323         eventdata = (u32 *)&chan->event.u.data;
324         scd_threshold = SCENE_CHANGE_THRESHOLD * MULTIPLICATION_FACTOR;
325
326         if (sad > scd_threshold)
327                 eventdata[0] = XSCD_SCENE_CHANGE;
328         else
329                 eventdata[0] = XSCD_NO_SCENE_CHANGE;
330
331         chan->event.type = V4L2_EVENT_XLNXSCD;
332         v4l2_subdev_notify_event(&chan->subdev, &chan->event);
333 }
334
335 void xscd_chan_irq_handler(struct xscd_chan *chan)
336 {
337         spin_lock(&chan->dmachan.lock);
338
339         if ((chan->xscd->memory_based && chan->dmachan.valid_interrupt) ||
340             !chan->xscd->memory_based) {
341                 spin_unlock(&chan->dmachan.lock);
342                 xscd_event_notify(chan);
343                 return;
344         }
345
346         spin_unlock(&chan->dmachan.lock);
347 }
348
349 /**
350  * xscd_chan_init - Initialize the V4L2 subdev for a channel
351  * @xscd: Pointer to the SCD device structure
352  * @chan_id: Channel id
353  * @node: device node
354  *
355  * Return: '0' on success and failure value on error
356  */
357 int xscd_chan_init(struct xscd_device *xscd, unsigned int chan_id,
358                    struct device_node *node)
359 {
360         struct xscd_chan *chan;
361         struct v4l2_subdev *subdev;
362         unsigned int num_pads;
363         int ret;
364
365         chan = devm_kzalloc(xscd->dev, sizeof(*chan), GFP_KERNEL);
366         if (!chan)
367                 return -ENOMEM;
368
369         xscd->chans[chan_id] = chan;
370
371         mutex_init(&chan->lock);
372         chan->xscd = xscd;
373         chan->id = chan_id;
374         chan->iomem = chan->xscd->iomem + chan->id * XSCD_CHAN_OFFSET;
375         chan->dmachan.id = chan->id;
376         chan->dmachan.iomem = chan->iomem;
377
378         xscd->channels[chan->id] = &chan->dmachan;
379
380         /* Initialize V4L2 subdevice and media entity */
381         subdev = &chan->subdev;
382         v4l2_subdev_init(subdev, &xscd_ops);
383         subdev->dev = chan->xscd->dev;
384         subdev->fwnode = of_fwnode_handle(node);
385         subdev->internal_ops = &xscd_internal_ops;
386         snprintf(subdev->name, sizeof(subdev->name), "xlnx-scdchan.%u",
387                  chan_id);
388         v4l2_set_subdevdata(subdev, chan);
389         subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
390
391         /* Initialize default format */
392         chan->format.code = MEDIA_BUS_FMT_VYYUYY8_1X24;
393         chan->format.field = V4L2_FIELD_NONE;
394         chan->format.width = XSCD_MAX_WIDTH;
395         chan->format.height = XSCD_MAX_HEIGHT;
396
397         /* Initialize media pads */
398         num_pads = xscd->memory_based ? 1 : 2;
399
400         chan->pads[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
401         if (!xscd->memory_based)
402                 chan->pads[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
403
404         ret = media_entity_pads_init(&subdev->entity, num_pads, chan->pads);
405         if (ret < 0)
406                 goto error;
407
408         subdev->entity.ops = &xscd_media_ops;
409         ret = v4l2_async_register_subdev(subdev);
410         if (ret < 0) {
411                 dev_err(chan->xscd->dev, "failed to register subdev\n");
412                 goto error;
413         }
414
415         dev_info(chan->xscd->dev, "Scene change detection channel found!\n");
416         return 0;
417
418 error:
419         media_entity_cleanup(&subdev->entity);
420         return ret;
421 }