This patch adds support for streaming scene change detection driver.
Signed-off-by: Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
Reviewed-by: Hyun Kwon <hyun.kwon@xilinx.com>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
*
* Copyright (C) 2018 Xilinx, Inc.
*
- * Author: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
*/
#include <linux/clk.h>
#include <media/v4l2-device.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_WIDTH_OFFSET 0x10
+#define XSCD_HEIGHT_OFFSET 0x18
+#define XSCD_STRIDE_OFFSET 0x20
+#define XSCD_VID_FMT_OFFSET 0x28
+#define XSCD_SUBSAMPLE_OFFSET 0x30
+
+/* Hardware video formats for memory based IP */
+#define XSCD_COLOR_FMT_Y8 24
+#define XSCD_COLOR_FMT_Y10 25
+
+/* Hardware video formats for streaming based IP */
+#define XSCD_COLOR_FMT_RGB 0
+#define XSCD_COLOR_FMT_YUV_444 1
+#define XSCD_COLOR_FMT_YUV_422 2
+#define XSCD_COLOR_FMT_YUV_420 4
+
+#define XSCD_V_SUBSAMPLING 16
+#define XSCD_BYTE_ALIGN 16
+
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Pad Operations
*/
return 0;
}
+static int xscd_chan_get_vid_fmt(u32 media_bus_fmt, bool memory_based)
+{
+ /*
+ * FIXME: We have same media bus codes for both 8bit and 10bit pixel
+ * formats. So, there is no way to differentiate between 8bit and 10bit
+ * formats based on media bus code. This will be fixed when we have
+ * dedicated media bus code for each format.
+ */
+ if (memory_based)
+ return XSCD_COLOR_FMT_Y8;
+
+ switch (media_bus_fmt) {
+ case MEDIA_BUS_FMT_VYYUYY8_1X24:
+ return XSCD_COLOR_FMT_YUV_420;
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ return XSCD_COLOR_FMT_YUV_422;
+ case MEDIA_BUS_FMT_VUY8_1X24:
+ return XSCD_COLOR_FMT_YUV_444;
+ case MEDIA_BUS_FMT_RBG888_1X24:
+ return XSCD_COLOR_FMT_RGB;
+ default:
+ return XSCD_COLOR_FMT_YUV_420;
+ }
+}
+
+/**
+ * xscd_chan_configure_params - Program parameters to HW registers
+ * @chan: Driver specific channel struct pointer
+ * @shared_data: Shared data
+ * @chan_offset: Register offset for a channel
+ */
+void xscd_chan_configure_params(struct xscd_chan *chan,
+ struct xscd_shared_data *shared_data,
+ u32 chan_offset)
+{
+ u32 vid_fmt, stride;
+
+ xscd_write(chan->iomem, XSCD_WIDTH_OFFSET + chan_offset,
+ chan->format.width);
+
+ /* Stride is required only for memory based IP, not for streaming IP */
+ if (shared_data->memory_based) {
+ stride = roundup(chan->format.width, XSCD_BYTE_ALIGN);
+ xscd_write(chan->iomem, XSCD_STRIDE_OFFSET + chan_offset,
+ stride);
+ }
+
+ xscd_write(chan->iomem, XSCD_HEIGHT_OFFSET + chan_offset,
+ chan->format.height);
+
+ /* Hardware video format */
+ vid_fmt = xscd_chan_get_vid_fmt(chan->format.code,
+ shared_data->memory_based);
+ xscd_write(chan->iomem, XSCD_VID_FMT_OFFSET + chan_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 + chan_offset,
+ XSCD_V_SUBSAMPLING);
+}
+
/* -----------------------------------------------------------------------------
* V4L2 Subdevice Operations
*/
struct xscd_chan *chan = to_chan(subdev);
struct xscd_shared_data *shared_data;
unsigned long flags;
+ u32 chan_offset;
/* TODO: Re-organise shared data in a better way */
shared_data = (struct xscd_shared_data *)chan->dev->parent->driver_data;
spin_lock_irqsave(&chan->dmachan.lock, flags);
- if (enable) {
- if (!shared_data->active_streams) {
- chan->dmachan.valid_interrupt = true;
- shared_data->active_streams++;
- xscd_dma_start_transfer(&chan->dmachan);
+ if (shared_data->memory_based) {
+ chan_offset = chan->id * XILINX_XSCD_CHAN_OFFSET;
+ xscd_chan_configure_params(chan, shared_data, chan_offset);
+ if (enable) {
+ if (!shared_data->active_streams) {
+ chan->dmachan.valid_interrupt = true;
+ shared_data->active_streams++;
+ xscd_dma_start_transfer(&chan->dmachan);
+ xscd_dma_reset(&chan->dmachan);
+ xscd_dma_chan_enable(&chan->dmachan,
+ BIT(chan->id));
+ xscd_dma_start(&chan->dmachan);
+ } else {
+ shared_data->active_streams++;
+ }
+ } else {
+ shared_data->active_streams--;
+ }
+ } else {
+ /* Streaming based */
+ if (enable) {
+ xscd_chan_configure_params(chan, shared_data, chan->id);
xscd_dma_reset(&chan->dmachan);
xscd_dma_chan_enable(&chan->dmachan, BIT(chan->id));
xscd_dma_start(&chan->dmachan);
} else {
- shared_data->active_streams++;
+ xscd_dma_halt(&chan->dmachan);
}
- } else {
- shared_data->active_streams--;
}
spin_unlock_irqrestore(&chan->dmachan.lock, flags);
{
struct xscd_chan *chan;
struct v4l2_subdev *subdev;
- struct v4l2_mbus_framefmt *default_format;
+ struct xscd_shared_data *shared_data;
int ret;
+ u32 num_pads;
+ shared_data = (struct xscd_shared_data *)pdev->dev.parent->driver_data;
chan = devm_kzalloc(&pdev->dev, sizeof(*chan), GFP_KERNEL);
if (!chan)
return -ENOMEM;
subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
/* Initialize default format */
- default_format = &chan->format;
- default_format->code = MEDIA_BUS_FMT_VYYUYY8_1X24;
- default_format->field = V4L2_FIELD_NONE;
- default_format->colorspace = V4L2_COLORSPACE_SRGB;
- default_format->width = XSCD_MAX_WIDTH;
- default_format->height = XSCD_MAX_HEIGHT;
- chan->format = *default_format;
- chan->pad.flags = MEDIA_PAD_FL_SINK;
- subdev->entity.ops = &xscd_media_ops;
+ 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 = shared_data->memory_based ? 1 : 2;
+ chan->pad = devm_kzalloc(&pdev->dev,
+ sizeof(struct media_pad) * num_pads,
+ GFP_KERNEL);
+ if (!chan->pad)
+ return -ENOMEM;
+
+ chan->pad[XVIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ if (!shared_data->memory_based)
+ chan->pad[XVIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
- ret = media_entity_pads_init(&subdev->entity, 1, &chan->pad);
+ ret = media_entity_pads_init(&subdev->entity, num_pads, chan->pad);
if (ret < 0)
goto error;
+ subdev->entity.ops = &xscd_media_ops;
ret = v4l2_async_register_subdev(subdev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register subdev\n");
*
* Copyright (C) 2018 Xilinx, Inc.
*
- * Author: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
*/
#include <linux/bitops.h>
#define XILINX_XSCD_CTRL_OFFSET 0x00
#define XILINX_XSCD_GIE_OFFSET 0x04
#define XILINX_XSCD_IE_OFFSET 0x08
-#define XILINX_XSCD_WIDTH_OFFSET 0x10
-#define XILINX_XSCD_HEIGHT_OFFSET 0x18
-#define XILINX_XSCD_STRIDE_OFFSET 0x20
-#define XILINX_XSCD_FMT_OFFSET 0x28
-#define XILINX_XSCD_SUBSAMPLE_OFFSET 0x30
#define XILINX_XSCD_ADDR_OFFSET 0x40
#define XILINX_XSCD_CHAN_EN_OFFSET 0x780
-#define XILINX_XSCD_FMT_Y8 24
-#define XILINX_XSCD_FMT_Y10 25
/* Control Registers */
#define XILINX_XSCD_CTRL_AP_START BIT(0)
#define XILINX_XSCD_CTRL_AP_DONE BIT(1)
#define XILINX_XSCD_CTRL_AP_IDLE BIT(2)
#define XILINX_XSCD_CTRL_AP_READY BIT(3)
+#define XILINX_XSCD_CTRL_AUTO_RESTART BIT(7)
#define XILINX_XSCD_GIE_EN BIT(0)
-#define XSCD_V_SUBSAMPLING 16
-
/**
* struct xscd_dma_device - Scene Change DMA device
* @regs: I/O mapped base address
* @common: DMA device structure
* @chan: Driver specific DMA channel
* @numchannels: Total number of channels
+ * @memory_based: Memory based or streaming based
*/
struct xscd_dma_device {
void __iomem *regs;
struct dma_device common;
struct xscd_dma_chan **chan;
u32 numchannels;
+ bool memory_based;
};
/**
{
struct xscd_dma_device *dev = data;
struct xscd_dma_chan *chan;
- u32 chan_en = 0, id;
- for (id = 0; id < dev->numchannels; id++) {
- chan = dev->chan[id];
- spin_lock(&chan->lock);
- chan->idle = true;
+ if (dev->memory_based) {
+ u32 chan_en = 0, id;
- if (chan->en && (!list_empty(&chan->pending_list))) {
- chan_en |= 1 << chan->id;
- chan->valid_interrupt = true;
- } else {
- chan->valid_interrupt = false;
- }
+ for (id = 0; id < dev->numchannels; id++) {
+ chan = dev->chan[id];
+ spin_lock(&chan->lock);
+ chan->idle = true;
- xscd_dma_start_transfer(chan);
- spin_unlock(&chan->lock);
- }
+ if (chan->en && (!list_empty(&chan->pending_list))) {
+ chan_en |= 1 << chan->id;
+ chan->valid_interrupt = true;
+ } else {
+ chan->valid_interrupt = false;
+ }
- if (chan_en) {
- xscd_dma_reset(chan);
- xscd_dma_chan_enable(chan, chan_en);
- xscd_dma_start(chan);
- }
+ xscd_dma_start_transfer(chan);
+ spin_unlock(&chan->lock);
+ }
- for (id = 0; id < dev->numchannels; id++) {
- chan = dev->chan[id];
- tasklet_schedule(&chan->tasklet);
+ if (chan_en) {
+ xscd_dma_reset(chan);
+ xscd_dma_chan_enable(chan, chan_en);
+ xscd_dma_start(chan);
+ }
+
+ for (id = 0; id < dev->numchannels; id++) {
+ chan = dev->chan[id];
+ tasklet_schedule(&chan->tasklet);
+ }
}
return IRQ_HANDLED;
xscd_write(chan->iomem, XILINX_XSCD_ADDR_OFFSET + chanoffset,
desc->sw.luma_plane_addr);
- /* HW expects these parameters to be same for one transaction */
- xscd_write(chan->iomem, (XILINX_XSCD_WIDTH_OFFSET + chanoffset),
- desc->sw.hsize);
- xscd_write(chan->iomem, (XILINX_XSCD_STRIDE_OFFSET + chanoffset),
- desc->sw.stride);
- xscd_write(chan->iomem, (XILINX_XSCD_HEIGHT_OFFSET + chanoffset),
- desc->sw.vsize);
- xscd_write(chan->iomem, (XILINX_XSCD_FMT_OFFSET + chanoffset),
- XILINX_XSCD_FMT_Y8);
-
- /* Number of times subsampled */
- xscd_write(chan->iomem, (XILINX_XSCD_SUBSAMPLE_OFFSET + chanoffset),
- XSCD_V_SUBSAMPLING);
-
list_del(&desc->node);
chan->staged_desc = desc;
}
{
struct xscd_dma_chan *chan = to_xilinx_chan(dchan);
struct xscd_dma_device *dev = chan->xdev;
- u32 chan_en = 0;
- u32 id;
+ u32 chan_en = 0, id;
for (id = 0; id < dev->numchannels; id++) {
chan = dev->chan[id];
*/
void xscd_dma_halt(struct xscd_dma_chan *chan)
{
- xscd_clr(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
- XILINX_XSCD_CTRL_AP_START);
+ struct xscd_dma_device *xdev = chan->xdev;
+
+ if (xdev->memory_based)
+ xscd_clr(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
+ XILINX_XSCD_CTRL_AP_START);
+ else
+ /* Streaming based */
+ xscd_clr(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
+ XILINX_XSCD_CTRL_AP_START |
+ XILINX_XSCD_CTRL_AUTO_RESTART);
+
chan->idle = true;
}
*/
void xscd_dma_start(struct xscd_dma_chan *chan)
{
- xscd_set(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
- XILINX_XSCD_CTRL_AP_START);
+ struct xscd_dma_device *xdev = chan->xdev;
+
+ if (xdev->memory_based)
+ xscd_set(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
+ XILINX_XSCD_CTRL_AP_START);
+ else
+ /* Streaming based */
+ xscd_set(chan->iomem, XILINX_XSCD_CTRL_OFFSET,
+ XILINX_XSCD_CTRL_AP_START |
+ XILINX_XSCD_CTRL_AUTO_RESTART);
+
chan->idle = false;
}
shared_data = (struct xscd_shared_data *)pdev->dev.parent->driver_data;
xdev->regs = shared_data->iomem;
xdev->chan = shared_data->dma_chan_list;
+ xdev->memory_based = shared_data->memory_based;
dma_set_mask(xdev->dev, DMA_BIT_MASK(32));
/* Initialize the DMA engine */
*
* Copyright (C) 2018 Xilinx, Inc.
*
- * Author: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
*/
#include "xilinx-scenechange.h"
}
static
-struct platform_device *xlnx_scdma_device_init(struct platform_device *pdev,
- struct device_node *node)
+struct platform_device *xilinx_scdma_device_init(struct platform_device *pdev,
+ struct device_node *node)
{
struct platform_device *dma;
int ret;
struct device_node *node = xscd->dev->of_node;
int ret;
- xscd->memorybased = of_property_read_bool(node, "xlnx,memorybased");
+ xscd->shared_data.memory_based =
+ of_property_read_bool(node, "xlnx,memorybased");
xscd->rst_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(xscd->rst_gpio)) {
if (PTR_ERR(xscd->rst_gpio) != -EPROBE_DEFER)
if (!xscd)
return -ENOMEM;
- /*
- * Memory based is enabled by default, this can be used for streaming
- * based driver
- */
- xscd->memorybased = true;
xscd->dev = &pdev->dev;
node = pdev->dev.of_node;
xscd->shared_data.iomem = xscd->iomem;
platform_set_drvdata(pdev, (void *)&xscd->shared_data);
- for_each_child_of_node(node, subdev_node) {
+ if (xscd->shared_data.memory_based) {
+ for_each_child_of_node(node, subdev_node) {
+ subdev = xscd_chan_alloc(pdev, subdev_node, id);
+ if (IS_ERR(subdev)) {
+ dev_err(&pdev->dev,
+ "Failed to initialize subdev@%d\n", id);
+ ret = PTR_ERR(subdev);
+ goto cleanup;
+ }
+ xscd->subdevs[id] = subdev;
+ id++;
+ }
+ } else {
+ /* Streaming based */
+ subdev_node = of_get_next_child(node, NULL);
subdev = xscd_chan_alloc(pdev, subdev_node, id);
if (IS_ERR(subdev)) {
dev_err(&pdev->dev,
goto cleanup;
}
xscd->subdevs[id] = subdev;
- id++;
}
- if (xscd->memorybased) {
- xscd->dma_device = xlnx_scdma_device_init(pdev, xscd->dma_node);
- if (IS_ERR(xscd->dma_node)) {
- ret = IS_ERR(xscd->dma_node);
- dev_err(&pdev->dev, "Failed to initialize the DMA\n");
- goto cleanup;
- }
- }
+ xscd->dma_device = xilinx_scdma_device_init(pdev, xscd->dma_node);
+ if (IS_ERR(xscd->dma_node))
+ dev_err(&pdev->dev, "Failed to initialize the DMA\n");
dev_info(xscd->dev, "scene change detect device found!\n");
return 0;
struct xscd_device *xscd = platform_get_drvdata(pdev);
u32 i;
- if (xscd->memorybased) {
- xilinx_scdma_device_exit(xscd->dma_device);
- xscd->dma_node = NULL;
- }
+ xilinx_scdma_device_exit(xscd->dma_device);
+ xscd->dma_node = NULL;
for (i = 0; i < xscd->numstreams; i++)
xscd_chan_remove(xscd->subdevs[i]);
*
* Copyright (C) 2018 Xilinx, Inc.
*
- * Author: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Authors: Anand Ashok Dumbre <anand.ashok.dumbre@xilinx.com>
+ * Satish Kumar Nagireddy <satish.nagireddy.nagireddy@xilinx.com>
*/
#ifndef _XILINX_SCENECHANGE_H_
* @iomem: device I/O register space remapped to kernel virtual memory
* @dma_chan_list: List of DMA channels available
* @active_streams: Number of active streams
+ * @memory_based: Flag to identify memory based mode
*/
struct xscd_shared_data {
void __iomem *iomem;
struct xscd_dma_chan *dma_chan_list[XSCD_MAX_CHANNELS];
u8 active_streams;
+ bool memory_based;
};
/**
* struct xscd_device - Xilinx Scene Change Detection device structure
* @iomem: device I/O register space remapped to kernel virtual memory
- * @memorybased: Flag to identify memory based mode
* @numstreams: Number of streams in the design
* @irq: Device IRQ
* @dev: (OF) device
*/
struct xscd_device {
void __iomem *iomem;
- bool memorybased;
int numstreams;
int irq;
struct device *dev;
struct xscd_dma_chan {
struct xscd_dma_device *xdev;
void __iomem *iomem;
+
/* Descriptor operation Lock */
spinlock_t lock;
struct list_head chan_node;
void __iomem *iomem;
struct device *dev;
struct v4l2_subdev subdev;
- struct media_pad pad;
+ struct media_pad *pad;
struct v4l2_mbus_framefmt format;
struct v4l2_event event;
struct xscd_dma_chan dmachan;